Android自定義控件之小說(shuō)書(shū)架實(shí)現(xiàn)示例詳解
前言
在手機(jī)看小說(shuō)的時(shí)候,看到一個(gè)很有意思的效果,在UC瀏覽器切換到小說(shuō)書(shū)架時(shí)候,可以在這個(gè)界面手指長(zhǎng)按一本書(shū)拖拽它,當(dāng)拖拽到其他小說(shuō)后面時(shí)候??梢詫⑵渌≌f(shuō)前置,拖拽的小說(shuō)到該位置上。功能效果大致如下圖所示:

功能分析
通過(guò)運(yùn)行圖可以看出,該程序主要功能包括
1.按照網(wǎng)格布局展示小說(shuō)信息
2.手指長(zhǎng)按單個(gè)小說(shuō)時(shí),可拖拽該小說(shuō),并且手指松開(kāi)時(shí),將拖拽小說(shuō)插入到該位置,其他小說(shuō)依次向移動(dòng)
3.選中要?jiǎng)h除的小說(shuō),點(diǎn)擊刪除按鈕刪除
其中有些難度的是小說(shuō)的拖拽,主要是拖拽需要注意的地方比較多。
代碼實(shí)現(xiàn)
小說(shuō)的展示
我這里是使用RecyclerView實(shí)現(xiàn),只不過(guò)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"/>
小說(shuō)拖拽BookShelfItemTouchHelper實(shí)現(xiàn)
RecyclerView想要實(shí)現(xiàn)拖拽功能需要寫(xiě)一個(gè)繼承ItemTouchHelper.Callback的類,這里把這個(gè)類命名為BookShelfItemTouchHelper。如果想要一個(gè)RecyclerView可以實(shí)現(xiàn)拖拽,可以給這個(gè)RecyclerView添加ItemTouchHelper,binding.rlBookshelf是想要添加拖拽效果的RecyclerView,設(shè)置代碼如下:
val itemTouchHelper = ItemTouchHelper(BookShelfItemTouchHelper(books,adapter)) itemTouchHelper.attachToRecyclerView(binding.rlBookshelf)
關(guān)于BookShelfItemTouchHelper這個(gè)類的實(shí)現(xiàn),需要重寫(xiě)下面幾個(gè)方法:
getMovementFlags:可以拖動(dòng)和滑動(dòng)的方向,最后通過(guò) makeMovementFlags 方法將拖拽和滑動(dòng)方向匯總起來(lái)。代碼里面是設(shè)置可以上下左右拖動(dòng),不設(shè)置滑動(dòng)。
onMove:這個(gè)方法是,當(dāng)手指移動(dòng)到某個(gè)item上時(shí),會(huì)觸發(fā)這個(gè)函數(shù)(個(gè)人理解這個(gè)函數(shù)觸發(fā)時(shí)機(jī)是,當(dāng)手指拖拽item在目標(biāo)view上停留了一小會(huì)),這個(gè)方法里面viewHolder參數(shù)是手指拖拽item的ViewHolde,target是目標(biāo)item的ViewHolder。在這里我們是把拖拽item放到目標(biāo)item位置上,并返回true。方法末尾,還需要調(diào)用Adapter的notifyItemMoved()方法,告訴RecyclerView這兩個(gè)item發(fā)生了變換,并重繪。關(guān)于托拽item與目標(biāo)item位置變換,如果我們把拖動(dòng)的item位置看為fromPosition,把目標(biāo)item的位置看為toPosition。我們需要把拖拽item放到目標(biāo)item位置上,并比較fromPosition和toPosition大小來(lái)決定,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會(huì)觸發(fā)這個(gè)方法,在這個(gè)方法里需要調(diào)用Adapter的notifyItemRangeChanged()方法來(lái)批量更新,item位置變換過(guò)程中受影響的數(shù)據(jù)。
onSelectedChanged():當(dāng)手指長(zhǎng)按選中item時(shí)會(huì)觸發(fā)這個(gè)方法,這個(gè)方法中,我修改了item的背景色并稍微擴(kuò)大item的寬高。
clearView:當(dāng)手指松開(kāi)時(shí)會(huì)觸發(fā)該方法,在這個(gè)方法里面,是恢復(fù)item的寬高及背景色。
interpolateOutOfBoundsScroll:這個(gè)方法是可以設(shè)置滾動(dòng)速度,如果不修改的話,會(huì)發(fā)現(xiàn)拖拽item到頂部或底部時(shí)候,向上或下的速度很慢,在這里設(shè)置快一些。
下面是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;//不響應(yīng)滑動(dòng)方向
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或者交互動(dòng)畫(huà)結(jié)束時(shí)調(diào)用 viewHolder是釋放的item的ViewHolder對(duì)象*/
@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);
}
/** 修改滾動(dòng)速度 下面是固定了劃動(dòng)速度*/
@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;
}
}
小說(shuō)刪除
小說(shuō)刪除相對(duì)簡(jiǎn)單,是在Adapter中定義了一個(gè)方法,當(dāng)點(diǎn)擊刪除按鈕時(shí)會(huì)調(diào)用該方法,該方法內(nèi)部時(shí)會(huì)判斷當(dāng)刪除數(shù)據(jù)長(zhǎng)度大于0時(shí),從Adapter接收的數(shù)據(jù)集里面移除刪除數(shù)據(jù),代碼如下:
/** 刪除選中的數(shù)據(jù) */
fun deleted(){
if (deletDatas.size>0){
for (data in deletDatas){
datas.remove(data)
}
deletDatas.clear()
notifyDataSetChanged()
}
}
總結(jié)
在實(shí)現(xiàn)小說(shuō)書(shū)架功能時(shí),遇到一些需要注意的地方,在此記錄下。
一開(kāi)始在實(shí)現(xiàn)功能時(shí)候,將下面的代碼,放到了onMove方法中執(zhí)行,這樣會(huì)有一個(gè)問(wèn)題手指拖拽時(shí),手指沒(méi)有松開(kāi),拖拽就結(jié)束,猜測(cè)是因?yàn)橥蟿?dòng)item時(shí),系統(tǒng)會(huì)重繪列表,但下面代碼會(huì)重新排序更新位置,就與拖拽產(chǎn)生沖突,導(dǎo)致拖拽結(jié)束。
bookshelfAdapter.notifyItemRangeChanged(Math.min(fromPos, toPos), Math.abs(fromPos - toPos) + 1);
另一個(gè)地方是,我想通過(guò)ItemDecoration給ReyclerView添加裝飾時(shí),在onDrawOver方法給列表每行開(kāi)頭小說(shuō)添加裝飾后,在拖動(dòng)時(shí)候發(fā)現(xiàn),不是開(kāi)頭的小說(shuō)也會(huì)出現(xiàn)這個(gè)效果,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);
}
}
}
拖拽時(shí)效果圖如下所示:

查閱資料時(shí),沒(méi)有找到解決辦法,只找到拖拽時(shí),會(huì)多次調(diào)用ItemDecoration的onDrawOver方法,目前只在拖拽的時(shí)候盡量不使用ItemDecoration。
項(xiàng)目代碼地址 github。
以上就是Android自定義控件之小說(shuō)書(shū)架的詳細(xì)內(nèi)容,更多關(guān)于Android自定義控件小說(shuō)書(shū)架的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于RxJava框架實(shí)現(xiàn)獲取驗(yàn)證碼的輔助類
這篇文章主要為大家詳細(xì)介紹了基于RxJava框架實(shí)現(xiàn)獲取驗(yàn)證碼的輔助類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
android開(kāi)發(fā)教程之系統(tǒng)資源的使用方法 android資源文件
這篇文章主要介紹了android中的系統(tǒng)資源的使用方法,包括顏色資源 、字符串資源、尺寸資源、XML資源文件,需要的朋友可以參考下2014-02-02
XListView實(shí)現(xiàn)網(wǎng)絡(luò)加載圖片和下拉刷新
這篇文章主要為大家詳細(xì)介紹了XListView實(shí)現(xiàn)網(wǎng)絡(luò)加載圖片和下拉刷新,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11
Android應(yīng)用自動(dòng)跳轉(zhuǎn)到應(yīng)用市場(chǎng)詳情頁(yè)面的方法
最近在工作中遇到一個(gè)需求,推廣部門(mén)要求實(shí)現(xiàn)應(yīng)用自動(dòng)跳轉(zhuǎn)到應(yīng)用市場(chǎng)詳情頁(yè)面,通過(guò)查找一些資料,實(shí)現(xiàn)出來(lái)了,覺(jué)得有必要整理下方便以后或者有需要的朋友們參考借鑒,下面來(lái)一起詳細(xì)看看Android應(yīng)用自動(dòng)跳轉(zhuǎn)到應(yīng)用市場(chǎng)詳情頁(yè)面的方法吧。2016-12-12
Android開(kāi)發(fā)之關(guān)于項(xiàng)目
本文是此系列文章的第二篇,給大家介紹的是項(xiàng)目相關(guān)的內(nèi)容,非常的細(xì)致全面,有需要的小伙伴可以參考下2016-02-02
android中DownloadManager實(shí)現(xiàn)版本更新,監(jiān)聽(tīng)下載進(jìn)度實(shí)例
本篇文章主要介紹了android中DownloadManager實(shí)現(xiàn)版本更新,監(jiān)聽(tīng)下載進(jìn)度實(shí)例。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03
實(shí)例解析如何在Android應(yīng)用中實(shí)現(xiàn)彈幕動(dòng)畫(huà)效果
這篇文章主要介紹了如何在Android應(yīng)用中實(shí)現(xiàn)彈幕動(dòng)畫(huà)效果的實(shí)例,文中是利用RelativeLayout布局然后控制ViewGroup中view的顯示,細(xì)節(jié)展示得比較詳細(xì),需要的朋友可以參考下2016-04-04

