Android簡(jiǎn)單實(shí)現(xiàn)菜單拖拽排序的功能
1、效果

2、簡(jiǎn)介
本文主角是ItemTouchHelper。
它是RecyclerView對(duì)于item交互處理的一個(gè)「輔助類」,主要用于拖拽以及滑動(dòng)處理。
以接口實(shí)現(xiàn)的方式,達(dá)到配置簡(jiǎn)單、邏輯解耦、職責(zé)分明的效果,并且支持所有的布局方式。
3、功能拆解

4、功能實(shí)現(xiàn)
4.1、實(shí)現(xiàn)接口
自定義一個(gè)類,實(shí)現(xiàn)ItemTouchHelper.Callback接口,然后在實(shí)現(xiàn)方法中根據(jù)需求簡(jiǎn)單配置即可。
class DragCallBack(adapter: DragAdapter, data: MutableList<String>) : ItemTouchHelper.Callback() {
}ItemTouchHelper.Callback必須實(shí)現(xiàn)的3個(gè)方法:
- getMovementFlags
- onMove
- onSwiped
其他方法還有onSelectedChanged、clearView等
4.1.1、getMovementFlags
用于創(chuàng)建交互方式,交互方式分為兩種:
- 拖拽,網(wǎng)格布局支持上下左右,列表只支持上下(LEFT、UP、RIGHT、DOWN)
- 滑動(dòng),只支持前后(START、END)
最后,通過makeMovementFlags把結(jié)果返回回去,makeMovementFlags接收兩個(gè)參數(shù),dragFlags和swipeFlags,即上面拖拽和滑動(dòng)組合的標(biāo)志位。
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
var dragFlags = 0
var swipeFlags = 0
when (recyclerView.layoutManager) {
is GridLayoutManager -> {
// 網(wǎng)格布局
dragFlags = ItemTouchHelper.LEFT or ItemTouchHelper.UP or ItemTouchHelper.RIGHT or ItemTouchHelper.DOWN
return makeMovementFlags(dragFlags, swipeFlags)
}
is LinearLayoutManager -> {
// 線性布局
dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
return makeMovementFlags(dragFlags, swipeFlags)
}
else -> {
// 其他情況可自行處理
return 0
}
}
}4.1.2、onMove
拖拽時(shí)回調(diào),這里我們主要對(duì)起始位置和目標(biāo)位置的item做一個(gè)數(shù)據(jù)交換,然后刷新視圖顯示。
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
// 起始位置
val fromPosition = viewHolder.adapterPosition
// 結(jié)束位置
val toPosition = target.adapterPosition
// 固定位置
if (fromPosition == mAdapter.fixedPosition || toPosition == mAdapter.fixedPosition) {
return false
}
// 根據(jù)滑動(dòng)方向 交換數(shù)據(jù)
if (fromPosition < toPosition) {
// 含頭不含尾
for (index in fromPosition until toPosition) {
Collections.swap(mData, index, index + 1)
}
} else {
// 含頭不含尾
for (index in fromPosition downTo toPosition + 1) {
Collections.swap(mData, index, index - 1)
}
}
// 刷新布局
mAdapter.notifyItemMoved(fromPosition, toPosition)
return true
}4.1.3、onSwiped
滑動(dòng)時(shí)回調(diào),這個(gè)回調(diào)方法里主要是做數(shù)據(jù)和視圖的更新操作。
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
if (direction == ItemTouchHelper.START) {
Log.i(TAG, "START--->向左滑")
} else {
Log.i(TAG, "END--->向右滑")
}
val position = viewHolder.adapterPosition
mData.removeAt(position)
mAdapter.notifyItemRemoved(position)
}4.2、綁定RecyclerView
上面接口實(shí)現(xiàn)部分我們已經(jīng)簡(jiǎn)單寫好了,邏輯也挺簡(jiǎn)單,總共不超過100行代碼。
接下來就是把這個(gè)輔助類綁定到RecyclerView。
RecyclerView顯示的實(shí)現(xiàn)就是基礎(chǔ)的樣式,就不展開了,可以查看源碼。
val dragCallBack = DragCallBack(mAdapter, list)
val itemTouchHelper = ItemTouchHelper(dragCallBack)
itemTouchHelper.attachToRecyclerView(mBinding.recycleView)綁定只需要調(diào)用attachToRecyclerView就好了。
至此,簡(jiǎn)單的效果就已經(jīng)實(shí)現(xiàn)了。下面開始優(yōu)化和進(jìn)階的部分。
4.3、設(shè)置分割線
RecyclerView網(wǎng)格布局實(shí)現(xiàn)等分,我們一般先是自定義ItemDecoration,然后調(diào)用addItemDecoration來實(shí)現(xiàn)的。
但是我在實(shí)現(xiàn)效果的時(shí)候遇到一個(gè)問題,因?yàn)槲壹恿瞬季智袚Q的功能,在每次切換的時(shí)候,針對(duì)不同的布局分別設(shè)置layoutManager和ItemDecoration,這就導(dǎo)致隨著切換次數(shù)的增加,item的間隔就越大。
addItemDecoration,顧名思義是添加,通過查看源碼發(fā)現(xiàn)RecyclerView內(nèi)部是有一個(gè)ArrayList來維護(hù)的,所以當(dāng)我們重復(fù)調(diào)用addItemDecoration方法時(shí),分割線是以遞增的方式在增加的,并且在繪制的時(shí)候會(huì)從集合中遍歷所有的分割線繪制。
部分源碼:
@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
//...
}既然知道了問題所在,也大概想到了3種解決辦法:
- 調(diào)用addItemDecoration前,先調(diào)用removeItemDecoration方法remove掉之前所有的分割線
- 調(diào)用addItemDecoration(@NonNull ItemDecoration decor, int index),通過index來維護(hù)
- add時(shí)通過一個(gè)標(biāo)示來判斷,添加過就不添加了
好像可行,實(shí)際上并不太行...因?yàn)槭冀K都有兩個(gè)分割線實(shí)例。
我們?cè)賮硎崂硪幌拢?/strong>
- 兩種不同的布局
- 都有分割線
- 分割線只需設(shè)置一次
我想到另外一個(gè)辦法,不對(duì)RecyclerView做處理了,既然兩種布局都有分割線,是不是可以把分割線合二為一了,然后根據(jù)LayoutManager去繪制不同的分割線?
理論上是可行的,事實(shí)上也確實(shí)可以...
自定義分割線:
class GridSpaceItemDecoration(private val spanCount: Int, private val spacing: Int = 20, private var includeEdge: Boolean = false) :
RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, recyclerView: RecyclerView, state: RecyclerView.State) {
recyclerView.layoutManager?.let {
when (recyclerView.layoutManager) {
is GridLayoutManager -> {
val position = recyclerView.getChildAdapterPosition(view) // 獲取item在adapter中的位置
val column = position % spanCount // item所在的列
if (includeEdge) {
outRect.left = spacing - column * spacing / spanCount
outRect.right = (column + 1) * spacing / spanCount
if (position < spanCount) {
outRect.top = spacing
}
outRect.bottom = spacing
} else {
outRect.left = column * spacing / spanCount
outRect.right = spacing - (column + 1) * spacing / spanCount
if (position >= spanCount) {
outRect.top = spanCount
}
outRect.bottom = spacing
}
}
is LinearLayoutManager -> {
outRect.top = spanCount
outRect.bottom = spacing
}
}
}
}
}4.4、選中放大/背景變色
為了提升用戶體驗(yàn),可以在拖拽的時(shí)候告訴用戶當(dāng)前拖拽的是哪個(gè)item,比如選中的item放大、背景高亮等。
- 網(wǎng)格布局,選中變大
- 列表布局,背景變色
這里用到ItemTouchHelper.Callback中的兩個(gè)方法,onSelectedChanged和clearView,我們需要在選中時(shí)改變視圖顯示,結(jié)束時(shí)再恢復(fù)。
4.4.1、onSelectedChanged
拖拽或滑動(dòng) 發(fā)生改變時(shí)回調(diào),這時(shí)我們可以修改item的視圖
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder?.let {
// 因?yàn)槟貌坏絩ecyclerView,無法通過recyclerView.layoutManager來判斷是什么布局,所以用item的寬度來判斷
// itemView.width > 500 用這個(gè)來判斷是否是線性布局,實(shí)際取值自己看情況
if (it.itemView.width > 500) {
// 線性布局 設(shè)置背景顏色
val drawable = it.itemView.background as GradientDrawable
drawable.color = ContextCompat.getColorStateList(it.itemView.context, R.color.greenDark)
} else {
// 網(wǎng)格布局 設(shè)置選中放大
ViewCompat.animate(it.itemView).setDuration(200).scaleX(1.3F).scaleY(1.3F).start()
}
}
}
super.onSelectedChanged(viewHolder, actionState)
}actionState:
- ACTION_STATE_IDLE 空閑狀態(tài)
- ACTION_STATE_SWIPE 滑動(dòng)狀態(tài)
- ACTION_STATE_DRAG 拖拽狀態(tài)
4.4.2、clearView
拖拽或滑動(dòng) 結(jié)束時(shí)回調(diào),這時(shí)我們要把改變后的item視圖恢復(fù)到初始狀態(tài)
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
// 恢復(fù)顯示
// 這里不能用if判斷,因?yàn)镚ridLayoutManager是LinearLayoutManager的子類,改用when,類型推導(dǎo)有區(qū)別
when (recyclerView.layoutManager) {
is GridLayoutManager -> {
// 網(wǎng)格布局 設(shè)置選中大小
ViewCompat.animate(viewHolder.itemView).setDuration(200).scaleX(1F).scaleY(1F).start()
}
is LinearLayoutManager -> {
// 線性布局 設(shè)置背景顏色
val drawable = viewHolder.itemView.background as GradientDrawable
drawable.color = ContextCompat.getColorStateList(viewHolder.itemView.context, R.color.greenPrimary)
}
}
super.clearView(recyclerView, viewHolder)
}4.5、固定位置
在實(shí)際需求中,交互可能要求我們第一個(gè)菜單不可以變更順序,只能固定,比如效果中的第一個(gè)菜單「推薦」固定在首位這種情況。
4.5.1、修改adapter
定義一個(gè)固定值,并設(shè)置不同的背景色和其他菜單區(qū)分開。
class DragAdapter(private val mContext: Context, private val mList: List<String>) : RecyclerView.Adapter<DragAdapter.ViewHolder>() {
val fixedPosition = 0 // 固定菜單
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.mItemTextView.text = mList[position]
// 第一個(gè)固定菜單
val drawable = holder.mItemTextView.background as GradientDrawable
if (holder.adapterPosition == 0) {
drawable.color = ContextCompat.getColorStateList(mContext, R.color.greenAccent)
}else{
drawable.color = ContextCompat.getColorStateList(mContext, R.color.greenPrimary)
}
}
//...
}4.5.1、修改onMove回調(diào)
在onMove方法中判斷,只要是固定位置就直接返回false。
class DragCallBack(adapter: DragAdapter, data: MutableList<String>) : ItemTouchHelper.Callback() {
/**
* 拖動(dòng)時(shí)回調(diào)
*/
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
// 起始位置
val fromPosition = viewHolder.adapterPosition
// 結(jié)束位置
val toPosition = target.adapterPosition
// 固定位置
if (fromPosition == mAdapter.fixedPosition || toPosition == mAdapter.fixedPosition) {
return false
}
// ...
return true
}
}雖然第一個(gè)菜單無法交換位置了,但是它還是可以拖拽的。
效果實(shí)現(xiàn)了嗎,好像也實(shí)現(xiàn)了,可是又好像哪里不對(duì),就好像填寫完表單點(diǎn)擊提交時(shí)你告訴我格式不正確一樣,你不能一開始就告訴我嗎?
為了進(jìn)一步提升用戶體驗(yàn),可以讓固定位置不可以拖拽嗎?
可以,ItemTouchHelper.Callback中有兩個(gè)方法:
- isLongPressDragEnabled 是否可以長(zhǎng)按拖拽
- isItemViewSwipeEnabled 是否可以滑動(dòng)
這倆方法默認(rèn)都是true,所以即使不能交換位置,但默認(rèn)也是支持操作的。
4.5.3、重寫isLongPressDragEnabled
以拖拽舉例,我們需要重寫isLongPressDragEnabled方法把它禁掉,然后再非固定位置的時(shí)候去手動(dòng)開啟。
override fun isLongPressDragEnabled(): Boolean {
//return super.isLongPressDragEnabled()
return false
}禁掉之后什么時(shí)候再觸發(fā)呢?
因?yàn)槲覀儸F(xiàn)在的交互是長(zhǎng)按進(jìn)入編輯,那就需要在長(zhǎng)按事件中再調(diào)用startDrag手動(dòng)開啟
mAdapter.setOnItemClickListener(object : DragAdapter.OnItemClickListener {
//...
override fun onItemLongClick(holder: DragAdapter.ViewHolder) {
if (holder.adapterPosition != mAdapter.fixedPosition) {
itemTouchHelper.startDrag(holder)
}
}
})ok,這樣就完美實(shí)現(xiàn)了。
4.6、其他
4.6.1、position
因?yàn)橛型献Р僮鳎聵?biāo)其實(shí)是變化的,在做相應(yīng)的操作時(shí),要取實(shí)時(shí)位置
holder.adapterPosition
4.6.2、重置
不管是拖拽還是滑動(dòng),其實(shí)本質(zhì)都是對(duì)Adapter內(nèi)已填充的數(shù)據(jù)進(jìn)行操作,實(shí)時(shí)數(shù)據(jù)通過Adapter獲取即可。
如果想要實(shí)現(xiàn)重置功能,直接拿最開始的原始數(shù)據(jù)重新塞給Adapter即可。
5、源碼探索
看源碼時(shí),找對(duì)一個(gè)切入點(diǎn),往往能達(dá)到事半功倍的效果。
這里就從綁定RecyclerView開始吧
val dragCallBack = DragCallBack(mAdapter, list)
val itemTouchHelper = ItemTouchHelper(dragCallBack)
itemTouchHelper.attachToRecyclerView(mBinding.recycleView)實(shí)例化ItemTouchHelper,然后調(diào)用其attachToRecyclerView方法綁定到RecyclerView。
5.1、attachToRecyclerView
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
if (mRecyclerView == recyclerView) {
return; // nothing to do
}
if (mRecyclerView != null) {
destroyCallbacks();
}
mRecyclerView = recyclerView;
if (recyclerView != null) {
final Resources resources = recyclerView.getResources();
mSwipeEscapeVelocity = resources.getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
mMaxSwipeVelocity = resources.getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
setupCallbacks();
}
}這段代碼其實(shí)有點(diǎn)意思的,解讀一下:
- 第一個(gè)if判斷,避免重復(fù)操作,直接return
- 第二個(gè)if判斷,調(diào)用了destroyCallbacks,在destroyCallbacks里面做了一些移除和回收操作,說明只能綁定到一個(gè)RecyclerView;同時(shí),注意這里判斷的主體是mRecyclerView,不是我們傳進(jìn)來的recyclerView,而且我們傳進(jìn)來的recyclerView是支持Nullable的,所以我們可以傳個(gè)空值走到destroyCallbacks里來做解綁操作
- 第三個(gè)if判斷,當(dāng)我們傳的recyclerView不為空時(shí),調(diào)用setupCallbacks
5.2、setupCallbacks
private void setupCallbacks() {
ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
mSlop = vc.getScaledTouchSlop();
mRecyclerView.addItemDecoration(this);
mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
mRecyclerView.addOnChildAttachStateChangeListener(this);
startGestureDetection();
}這個(gè)方法里已經(jīng)大概可以看出內(nèi)部實(shí)現(xiàn)原理了。
兩個(gè)關(guān)鍵點(diǎn):
- addOnItemTouchListener
- startGestureDetection
通過觸摸和手勢(shì)識(shí)別來處理交互顯示。
5.3、mOnItemTouchListener
private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) {
mGestureDetector.onTouchEvent(event);
if (action == MotionEvent.ACTION_DOWN) {
//...
if (mSelected == null) {
if (animation != null) {
//...
select(animation.mViewHolder, animation.mActionState);
}
}
} else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
select(null, ACTION_STATE_IDLE);
} else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
//...
if (index >= 0) {
checkSelectForSwipe(action, event, index);
}
}
return mSelected != null;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) {
mGestureDetector.onTouchEvent(event);
//...
if (activePointerIndex >= 0) {
checkSelectForSwipe(action, event, activePointerIndex);
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
if (activePointerIndex >= 0) {
moveIfNecessary(viewHolder);
}
break;
}
//...
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
select(null, ACTION_STATE_IDLE);
}
};這段代碼刪減之后還是有點(diǎn)多,不過沒關(guān)系,提煉一下,核心通過判斷MotionEvent調(diào)用了幾個(gè)方法:
- select
- checkSelectForSwipe
- moveIfNecessary
5.3.1、select
void select(@Nullable ViewHolder selected, int actionState) {
if (selected == mSelected && actionState == mActionState) {
return;
}
//...
if (mSelected != null) {
if (prevSelected.itemView.getParent() != null) {
final float targetTranslateX, targetTranslateY;
switch (swipeDir) {
case LEFT:
case RIGHT:
case START:
case END:
targetTranslateY = 0;
targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
break;
//...
}
//...
} else {
removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
mCallback.clearView(mRecyclerView, prevSelected);
}
}
//...
mCallback.onSelectedChanged(mSelected, mActionState);
mRecyclerView.invalidate();
}這里面主要是在拖拽或滑動(dòng)時(shí)對(duì)translateX/Y的計(jì)算和處理,然后通過mCallback.clearView和mCallback.onSelectedChanged回調(diào)給我們,最后調(diào)用invalidate()實(shí)時(shí)刷新。
5.3.2、checkSelectForSwipe
void checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
//...
if (absDx < mSlop && absDy < mSlop) {
return;
}
if (absDx > absDy) {
if (dx < 0 && (swipeFlags & LEFT) == 0) {
return;
}
if (dx > 0 && (swipeFlags & RIGHT) == 0) {
return;
}
} else {
if (dy < 0 && (swipeFlags & UP) == 0) {
return;
}
if (dy > 0 && (swipeFlags & DOWN) == 0) {
return;
}
}
select(vh, ACTION_STATE_SWIPE);
}這里是滑動(dòng)處理的check,最后也是收斂到select()方法統(tǒng)一處理。
5.3.3、moveIfNecessary
void moveIfNecessary(ViewHolder viewHolder) {
if (mRecyclerView.isLayoutRequested()) {
return;
}
if (mActionState != ACTION_STATE_DRAG) {
return;
}
//...
if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
// keep target visible
mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
target, toPosition, x, y);
}
}這里檢查拖拽時(shí)是否需要交換item,通過mCallback.onMoved回調(diào)給我們。
5.4、startGestureDetection
private void startGestureDetection() {
mItemTouchHelperGestureListener = new ItemTouchHelperGestureListener();
mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(),
mItemTouchHelperGestureListener);
}5.4.1、ItemTouchHelperGestureListener
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
//...
@Override
public void onLongPress(MotionEvent e) {
//...
View child = findChildView(e);
if (child != null) {
ViewHolder vh = mRecyclerView.getChildViewHolder(child);
if (vh != null) {
//...
if (pointerId == mActivePointerId) {
//...
if (mCallback.isLongPressDragEnabled()) {
select(vh, ACTION_STATE_DRAG);
}
}
}
}
}
}這里主要是對(duì)長(zhǎng)按事件的處理,最后也是收斂到select()方法統(tǒng)一處理。
5.5、源碼小結(jié)
- 綁定RecyclerView
- 注冊(cè)觸摸手勢(shì)監(jiān)聽
- 根據(jù)手勢(shì),先是內(nèi)部處理各種校驗(yàn)、位置計(jì)算、動(dòng)畫處理、刷新等,然后回調(diào)給ItemTouchHelper.Callback
事兒大概就是這么個(gè)事兒,主要工作都是源碼幫我們做了,我們只需要在回調(diào)里根據(jù)結(jié)果處理業(yè)務(wù)邏輯即可。
到此這篇關(guān)于Android簡(jiǎn)單實(shí)現(xiàn)菜單拖拽排序的功能的文章就介紹到這了,更多相關(guān)Android拖拽排序功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android屏幕手勢(shì)檢測(cè)的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android屏幕手勢(shì)檢測(cè)的實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-11-11
Android實(shí)現(xiàn)定時(shí)自動(dòng)靜音小助手
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)定時(shí)自動(dòng)靜音小助手,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Android使用自定義view在指定時(shí)間內(nèi)勻速畫一條直線的實(shí)例代碼
這篇文章主要介紹了Android使用自定義view在指定時(shí)間內(nèi)勻速畫一條直線的實(shí)例代碼,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
Android編程獲取網(wǎng)絡(luò)時(shí)間實(shí)例分析
這篇文章主要介紹了Android編程獲取網(wǎng)絡(luò)時(shí)間,結(jié)合實(shí)例形式對(duì)比分析了Android通過訪問網(wǎng)絡(luò)及通過GPS獲取網(wǎng)絡(luò)時(shí)間的具體步驟與實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-01-01
Android性能優(yōu)化getResources()與Binder導(dǎo)致界面卡頓優(yōu)化
這篇文章主要為大家介紹了Android性能優(yōu)化getResources()與Binder導(dǎo)致界面卡頓優(yōu)化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
Android中的windowSoftInputMode屬性詳解
這篇文章主要介紹了Android中的windowSoftInputMode屬性詳解,本文對(duì)windowSoftInputMode的9個(gè)屬性做了詳細(xì)總結(jié),需要的朋友可以參考下2014-10-10
android 限制某個(gè)操作每天只能操作指定的次數(shù)(示例代碼詳解)
這篇文章主要介紹了android 限制某個(gè)操作每天只能操作指定的次數(shù),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
Android使用httpPost向服務(wù)器發(fā)送請(qǐng)求的方法
這篇文章主要介紹了Android使用httpPost向服務(wù)器發(fā)送請(qǐng)求的方法,實(shí)例分析了Android針對(duì)HttpPost類的操作技巧,需要的朋友可以參考下2015-12-12
Android音頻系統(tǒng)AudioTrack使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Android音頻系統(tǒng)AudioTrack的使用方法,如何使用AudioTrack進(jìn)行音頻播放,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07

