Android自定義控件之小說書架實現(xiàn)示例詳解
前言
在手機看小說的時候,看到一個很有意思的效果,在UC瀏覽器切換到小說書架時候,可以在這個界面手指長按一本書拖拽它,當拖拽到其他小說后面時候??梢詫⑵渌≌f前置,拖拽的小說到該位置上。功能效果大致如下圖所示:
功能分析
通過運行圖可以看出,該程序主要功能包括
1.按照網(wǎng)格布局展示小說信息
2.手指長按單個小說時,可拖拽該小說,并且手指松開時,將拖拽小說插入到該位置,其他小說依次向移動
3.選中要刪除的小說,點擊刪除按鈕刪除
其中有些難度的是小說的拖拽,主要是拖拽需要注意的地方比較多。
代碼實現(xiàn)
小說的展示
我這里是使用RecyclerView實現(xiàn),只不過layoutManager是使用GridLayoutManager,代碼如下:
<androidx.recyclerview.widget.RecyclerView android:id="@+id/rl_bookshelf" android:layout_width="match_parent" android:layout_height="wrap_content" app:spanCount="3" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" tools:listitem="@layout/item_bookshelf"/>
小說拖拽BookShelfItemTouchHelper實現(xiàn)
RecyclerView想要實現(xiàn)拖拽功能需要寫一個繼承ItemTouchHelper.Callback的類,這里把這個類命名為BookShelfItemTouchHelper。如果想要一個RecyclerView可以實現(xiàn)拖拽,可以給這個RecyclerView添加ItemTouchHelper,binding.rlBookshelf是想要添加拖拽效果的RecyclerView,設置代碼如下:
val itemTouchHelper = ItemTouchHelper(BookShelfItemTouchHelper(books,adapter)) itemTouchHelper.attachToRecyclerView(binding.rlBookshelf)
關于BookShelfItemTouchHelper這個類的實現(xiàn),需要重寫下面幾個方法:
getMovementFlags:可以拖動和滑動的方向,最后通過 makeMovementFlags 方法將拖拽和滑動方向匯總起來。代碼里面是設置可以上下左右拖動,不設置滑動。
onMove:這個方法是,當手指移動到某個item上時,會觸發(fā)這個函數(shù)(個人理解這個函數(shù)觸發(fā)時機是,當手指拖拽item在目標view上停留了一小會),這個方法里面viewHolder參數(shù)是手指拖拽item的ViewHolde,target是目標item的ViewHolder。在這里我們是把拖拽item放到目標item位置上,并返回true。方法末尾,還需要調(diào)用Adapter的notifyItemMoved()方法,告訴RecyclerView這兩個item發(fā)生了變換,并重繪。關于托拽item與目標item位置變換,如果我們把拖動的item位置看為fromPosition,把目標item的位置看為toPosition。我們需要把拖拽item放到目標item位置上,并比較fromPosition和toPosition大小來決定,fromPosition與toPositon之間item是前移還是后移。代碼如下:
@Override public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { int fromPosition = viewHolder.getAdapterPosition(); int toPosition = target.getAdapterPosition(); bookshelfAdapter.notifyItemRangeChanged(Math.min(fromPosition,toPosition),Math.abs(fromPosition - toPosition) + 1); if (fromPosition < toPosition){ for (int i = fromPosition;i<toPosition;i++){ Collections.swap(books,i,i+1); } }else { for (int i = fromPosition; i > toPosition;i--){ Collections.swap(books,i,i-1); } } bookshelfAdapter.notifyItemMoved(fromPosition,toPosition); return true; }
onMoved:onMove返回true會觸發(fā)這個方法,在這個方法里需要調(diào)用Adapter的notifyItemRangeChanged()方法來批量更新,item位置變換過程中受影響的數(shù)據(jù)。
onSelectedChanged():當手指長按選中item時會觸發(fā)這個方法,這個方法中,我修改了item的背景色并稍微擴大item的寬高。
clearView:當手指松開時會觸發(fā)該方法,在這個方法里面,是恢復item的寬高及背景色。
interpolateOutOfBoundsScroll:這個方法是可以設置滾動速度,如果不修改的話,會發(fā)現(xiàn)拖拽item到頂部或底部時候,向上或下的速度很慢,在這里設置快一些。
下面是BookShelfItemTouchHelper的完整代碼:
public class BookShelfItemTouchHelper extends ItemTouchHelper.Callback { private final String TAG = "BookShelfItemTouchHelper"; private List<Book> books; private BookshelfAdapter bookshelfAdapter; public BookShelfItemTouchHelper(List<Book> books, BookshelfAdapter bookshelfAdapter) { this.books = books; this.bookshelfAdapter = bookshelfAdapter; } @Override public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; int swipeFlags = 0;//不響應滑動方向 int flags = makeMovementFlags(dragFlags,swipeFlags); return flags; } @Override public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { int fromPosition = viewHolder.getAdapterPosition(); int toPosition = target.getAdapterPosition(); bookshelfAdapter.notifyItemRangeChanged(Math.min(fromPosition,toPosition),Math.abs(fromPosition - toPosition) + 1); if (fromPosition < toPosition){ for (int i = fromPosition;i<toPosition;i++){ Collections.swap(books,i,i+1); } }else { for (int i = fromPosition; i > toPosition;i--){ Collections.swap(books,i,i-1); } } bookshelfAdapter.notifyItemMoved(fromPosition,toPosition); return true; } @Override public void onMoved(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, int fromPos, @NonNull RecyclerView.ViewHolder target, int toPos, int x, int y) { super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y); bookshelfAdapter.notifyItemRangeChanged(Math.min(fromPos, toPos), Math.abs(fromPos - toPos) + 1); } /** 選中狀態(tài)改變通知 */ @Override public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) { super.onSelectedChanged(viewHolder, actionState); if (actionState == ItemTouchHelper.ACTION_STATE_DRAG){ int bgColor = viewHolder.itemView.getContext().getResources().getColor(R.color.text_blue); viewHolder.itemView.setScaleX(1.2f); viewHolder.itemView.setScaleY(1.2f); viewHolder.itemView.setBackgroundColor(bgColor); } } /** 手指釋放item或者交互動畫結(jié)束時調(diào)用 viewHolder是釋放的item的ViewHolder對象*/ @Override public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); viewHolder.itemView.setScaleX(1.0f); viewHolder.itemView.setScaleY(1.0f); int color = viewHolder.itemView.getContext().getResources().getColor(R.color.white); viewHolder.itemView.setBackgroundColor(color); } /** 修改滾動速度 下面是固定了劃動速度*/ @Override public int interpolateOutOfBoundsScroll(@NonNull RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) { final int direction = (int) Math.signum(viewSizeOutOfBounds); return 30 * direction; } }
小說刪除
小說刪除相對簡單,是在Adapter中定義了一個方法,當點擊刪除按鈕時會調(diào)用該方法,該方法內(nèi)部時會判斷當刪除數(shù)據(jù)長度大于0時,從Adapter接收的數(shù)據(jù)集里面移除刪除數(shù)據(jù),代碼如下:
/** 刪除選中的數(shù)據(jù) */ fun deleted(){ if (deletDatas.size>0){ for (data in deletDatas){ datas.remove(data) } deletDatas.clear() notifyDataSetChanged() } }
總結(jié)
在實現(xiàn)小說書架功能時,遇到一些需要注意的地方,在此記錄下。
一開始在實現(xiàn)功能時候,將下面的代碼,放到了onMove方法中執(zhí)行,這樣會有一個問題手指拖拽時,手指沒有松開,拖拽就結(jié)束,猜測是因為拖動item時,系統(tǒng)會重繪列表,但下面代碼會重新排序更新位置,就與拖拽產(chǎn)生沖突,導致拖拽結(jié)束。
bookshelfAdapter.notifyItemRangeChanged(Math.min(fromPos, toPos), Math.abs(fromPos - toPos) + 1);
另一個地方是,我想通過ItemDecoration給ReyclerView添加裝飾時,在onDrawOver方法給列表每行開頭小說添加裝飾后,在拖動時候發(fā)現(xiàn),不是開頭的小說也會出現(xiàn)這個效果,onDarwOver方法如下:
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { super.onDrawOver(c, parent, state); int childCount = parent.getChildCount(); for (int i=0;i<childCount;i++){ View child = parent.getChildAt(i); if (child!=null && i%row==0){ int startX = (child.getLeft()+child.getRight())/2 - decorationBmp.getWidth()/2; int startY = child.getTop() + child.getPaddingTop() - decorationBmp.getHeight()/2; c.drawBitmap(decorationBmp,startX,startY,paint); } } }
拖拽時效果圖如下所示:
查閱資料時,沒有找到解決辦法,只找到拖拽時,會多次調(diào)用ItemDecoration的onDrawOver方法,目前只在拖拽的時候盡量不使用ItemDecoration。
項目代碼地址 github。
以上就是Android自定義控件之小說書架的詳細內(nèi)容,更多關于Android自定義控件小說書架的資料請關注腳本之家其它相關文章!
相關文章
詳解Android應用中preference首選項的編寫方法
這篇文章主要介紹了Android應用中preference首選項的編寫方法,或許Apple將其翻譯為'偏好設置'更直觀些,即用戶對應用的一些個性化調(diào)整菜單,需要的朋友可以參考下2016-04-04Android Binder進程間通信工具AIDL使用示例深入分析
Binder作為Android 眾多的IPC通訊手段之一,在Framework的數(shù)據(jù)傳輸中起到極為關鍵的作用。Binder機制可謂是Android 知識體系里的重中之重,作為偏底層的基礎組件,平時我們很少關注它,而它卻是無處不在,也是Android 面試易考察的點之一2022-11-11Android開發(fā)實現(xiàn)長按返回鍵彈出關機框功能
這篇文章主要介紹了Android開發(fā)實現(xiàn)長按返回鍵彈出關機框功能,涉及Android針對長按事件的響應與處理相關操作技巧,需要的朋友可以參考下2017-09-09Android Studio報:“Attribute application@theme or @ icon ”問題的解
這篇文章主要給大家介紹了關于Android Studio報:“Attribute application@theme or @ icon ”問題的解決方法,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧。2017-12-12