===滑動拖拽===
Look, ItemTouchHelper | Android Developers
ItemTouchHelper
public class ItemTouchHelper
extends RecyclerView.ItemDecoration implements RecyclerView.OnChildAttachStateChangeListener
java.lang.Object
? android.support.v7.widget.RecyclerView.ItemDecoration
? android.support.v7.widget.helper.ItemTouchHelper
This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
It works with a RecyclerView and a Callback class, which configures what type of interactions are enabled and also receives events when user performs these actions.
Depending on which functionality you support, you should override onMove(RecyclerView, ViewHolder, ViewHolder) and / or onSwiped(ViewHolder, int).
This class is designed to work with any LayoutManager but for certain situations, it can be optimized for your custom LayoutManager by extending methods in the ItemTouchHelper.Callback class or implementing ItemTouchHelper.ViewDropHandler interface in your LayoutManager.
By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. You can customize these behaviors by overriding onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int, boolean) or onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean).
Most of the time you only need to override onChildDraw.
解釋:就是說這是一個支持RecyclerView的滑動刪除和拖拽的實體類脂信。
它靠一個回調(diào)來實現(xiàn)唇兑,也就是ItemTouchHelper.Callback | Android Developers
基于這個支持锨并,你還需要實現(xiàn)[onMove(RecyclerView, ViewHolder, ViewHolder)](https://link.zhihu.com/?target=https%3A//developer.android.google.cn/reference/android/support/v7/widget/helper/ItemTouchHelper.Callback.html%3Fhl%3Dzh-cn%23onMove%28android.support.v7.widget.RecyclerView%2C%2520android.support.v7.widget.RecyclerView.ViewHolder%2C%2520android.support.v7.widget.RecyclerView.ViewHolder%29)
and / or[onSwiped(ViewHolder, int)](https://link.zhihu.com/?target=https%3A//developer.android.google.cn/reference/android/support/v7/widget/helper/ItemTouchHelper.Callback.html%3Fhl%3Dzh-cn%23onSwiped%28android.support.v7.widget.RecyclerView.ViewHolder%2C%2520int%29)
方法。
這個類還支持特定解決方案的所有LayoutManager五垮,但是需要你繼承ItemTouchHelper.Callback或者實現(xiàn)ItemTouchHelper.ViewDropHandler 接口乍惊。
另外你通過重寫onChildDraw 方法可以實現(xiàn)移動屬性的自定義。
1. 所以創(chuàng)建一個這樣的實體對象就是像這樣:
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return 0;
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
}
});
2. 調(diào)用就是如下的方法咯, attach一下RecyclerView就可以啦...
void attachToRecyclerView(RecyclerView recyclerView)
Attaches the ItemTouchHelper to the provided RecyclerView.
還有其他方法如下放仗,先不管嘛润绎,慢慢來
Look2, 所以重點來了,就是這個 ItemTouchHelper.Callback | Android Developers 如果不想自定義匙监,其實有個官方簡單版ItemTouchHelper.SimpleCallback | Android Developers<u style="text-decoration: none; border-bottom: 1px dashed grey;"> 這個可能就不能針對某個條目單獨處理了咯....</u>
不過人家已經(jīng)把移動操作弄好了凡橱。另外滑動效果也處理了,你只需要將滑動的條目從adapter中刪除即可亭姥!參數(shù)就是滑動的方向和拖拽的方法處理(比如只能向上拖動稼钩,左滑右滑啥的),如下:
ItemTouchHelper itemTouchHelper2 = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
// remove from adapter
}
});
Then,如果我們希望列表類似如下:(底部的條目不能被滑動刪除达罗,也不能被移動坝撑,同時也不能被拖動的條目改變位置...)
1. 這個時候就需要我們自定義ItemTouchHelper.Callback | Android Developers
然后重點重寫如下三個方法,基本上就可以進行相關(guān)控制了粮揉。當然有些也可以簡單重寫巡李,比如isLongPressDragEnabled()這些,可以控制狀態(tài)扶认。
abstract int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
abstract boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
Called when ItemTouchHelper wants to move the dragged item from its old position to the new position.
abstract void onSwiped(RecyclerView.ViewHolder viewHolder, int direction)
Called when a ViewHolder is swiped by the user.
2. 我們?yōu)榱丝刂谱詈蟮臈l目不能操作的情況多糠,我們重點關(guān)注下getMovementFlags方法
getMovementFlags
added in version 24.1.0
int getMovementFlags (RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder)
Should return a composite flag which defines the enabled move directions in each state (idle, swiping, dragging).
Instead of composing this flag manually, you can use makeMovementFlags(int, int) or makeFlag(int, int).
This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next 8 bits are for SWIPE state and third 8 bits are for DRAG state. Each 8 bit sections can be constructed by simply OR'ing direction flags defined in ItemTouchHelper.
For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to swipe by swiping RIGHT, you can return:
makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
This means, allow right movement while IDLE and allow right and left movement while swiping.
這個屬性返回值就意味著你對該條目的操作狀態(tài)赴邻,比如我們獲取當前位置是最后一個條目,進行如下處理:(mList.size()需要你外部傳入鏈表mList喲)
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
//int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
//int swipeFlags = ItemTouchHelper.LEFT;
int[] flags;
if (viewHolder.getLayoutPosition() == (mList.size() - 1)){
flags = new int[]{0, 0};
}else{
flags = new int[]{ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT};
}
return makeMovementFlags(flags[0], flags[1]);
}
也就是說不管是滑動還是拖拽,只要是最后一個條目身堡,那么標志都為0海洼,也就是什么都不能干玉控!其他情況嫌套,正常左滑,拖拽即可誉察!
當然其實我們可以搞一個回調(diào)來返回想要的處理与涡,如果說很多頁面都是同樣的操作,倒是可以再次封裝一下,最后我們貼上我的自定義:
SimpleItemTouchHelperCallback.java
import android.graphics.Canvas;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
/*
*@Description: 側(cè)滑刪除輔助類
*@Author: hl
*@Time: 2019/1/4 15:52
*/
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
private Sthc_Movement sthc_movement;
public SimpleItemTouchHelperCallback(Sthc_Movement sthc_movement){
this.sthc_movement = sthc_movement;
}
/**
* 控制每個條目的可操作狀態(tài) - 滑動驼卖,拖拽等 來源->getFlag
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
//int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
//int swipeFlags = ItemTouchHelper.LEFT;
int[] flags = sthc_movement.getFlag(viewHolder.getLayoutPosition());
return makeMovementFlags(flags[0], flags[1]);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
sthc_movement.onMove(viewHolder, target);
return true;
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
sthc_movement.onSwiped(viewHolder, i);
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
final float alpha = 1 - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);
viewHolder.itemView.setTranslationX(dX);
}
}
public interface Sthc_Movement{
public void onMove(RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target);
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction);
public int[] getFlag(int postion);
}
}
LookEnd,具體使用如下(注意移動條目交換的操作就行)
///< 側(cè)滑刪除和拖拽排序
SimpleItemTouchHelper itemTouchHelper = new SimpleItemTouchHelper(new SimpleItemTouchHelperCallback(new SimpleItemTouchHelperCallback.Sthc_Movement() {
@Override
public void onMove(RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
///< 禁止拖動到新增菜單的底部
if (toPosition >= (mList.size() - 1)){
return;
}
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(mList, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(mList, i, i - 1);
}
}
baseAdapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
mList.remove(position);
baseAdapter.notifyItemRemoved(position);
}
/**
* 控制每個條目的可操作狀態(tài) - 滑動氨肌,拖拽等 對應(yīng)->getMovementFlags
* @param position
* @return
*/
@Override
public int[] getFlag(int position) {
if (position == (mList.size() - 1)){
return new int[]{0, 0};
}else{
return new int[]{ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT};
}
}
}));
itemTouchHelper.attachToRecyclerView(dailyItemsRv);
其中注意不允許其他條目在底部菜單下面,所以我增加了如下判斷:
基本上簡單需求是滿足了酌畜。對了儒飒,1. onChildDrawif (actionState == ItemTouchHelper.ACTION_STATE_SWIPE**) 的處理,是滑動過程alpha透明度的變化檩奠。滑動慢點可以看到效果附帽。埠戳。。
2. SimpleItemTouchHelper沒什么東東蕉扮,就簡單繼承了下ItemTouchHelper | Android Developers 后面或許還可以增加額外自定義處理整胃。
這個先簡單這樣認識下,畢竟小萌新都沒接觸過喳钟,有時候就是需要項目多實戰(zhàn)屁使。把常用的都搞搞,然后深入奔则,然后源碼剖析蛮寂,這樣應(yīng)該才能掌握的更好!
===上拉加載更多===
說起這個易茬,一般小萌新都是搬磚的酬蹋,只會用人家的框架 - 下拉刷新,上拉加載更多抽莱!我們來看看官方的下拉刷新SwipeRefreshLayout | Android Developers 這個簡單入門使用還好范抓,先不搞特別復雜的效果:
// 設(shè)置顏色屬性的時候一定要注意是引用了資源文件還是直接設(shè)置16進制的顏色,因為都是int值容易搞混
// 設(shè)置下拉進度的背景顏色食铐,默認就是白色的
dailyHisItemsSwr.setProgressBackgroundColorSchemeResource(android.R.color.white);
// 設(shè)置下拉進度的主題顏色
dailyHisItemsSwr.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary, R.color.colorPrimaryDark);
// 下拉時觸發(fā)SwipeRefreshLayout的下拉動畫匕垫,動畫完畢之后就會回調(diào)這個方法
dailyHisItemsSwr.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// 開始刷新,設(shè)置當前為刷新狀態(tài)
//swipeRefreshLayout.setRefreshing(true);
// if (startload){
// return;
// }
// 模擬下:
// 這里是主線程
// 一些比較耗時的操作虐呻,比如聯(lián)網(wǎng)獲取數(shù)據(jù)象泵,需要放到子線程去執(zhí)行
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// 加載數(shù)據(jù)
// 刷新數(shù)據(jù) baseAdapter.notifyDataSetChanged();
// 加載完數(shù)據(jù)設(shè)置為不刷新狀態(tài),將下拉進度收起來
dailyHisItemsSwr.setRefreshing(false);
}
}, 500);
// 這個不能寫在外邊铃慷,不然會直接收起來
//swipeRefreshLayout.setRefreshing(false);
}
});
基本就能看到下拉刷新的效果啦....
然后接著我們快速入門下上拉加載更多吧单芜,這個有點點麻煩,另外如果SwipeRefreshLayout + RecyclerView實現(xiàn)下拉刷新/上拉加載更多 - 可能還會遇到很多問題需要處理(比如下拉時禁止上拉犁柜,上拉加載過程中禁止下拉刷新洲鸠,還有就是上拉加載觸發(fā)的條件-當前如果沒有數(shù)據(jù),可能就不能上拉加載了,可能就是全屏顯示無數(shù)據(jù)扒腕,點擊刷新獲取了, 總之要實現(xiàn)一個效果體驗好的上拉加載/下拉刷新框架不是這么容易的)绢淀。 從目前小萌新的感覺來看,實現(xiàn)上拉加載更多動畫瘾腰,上拉停止動畫皆的,回彈,這個過程是需要監(jiān)聽持續(xù)touch事件的蹋盆,也就是說單純的靠監(jiān)聽RecyclerView的addOnScrollListener貌似不太行费薄,必須有持續(xù)監(jiān)聽。 后面要專門搞搞這個上拉加載更多的效果....
說這么多栖雾,開始吧...**上拉加載更多楞抡。 **目前的方案基本都是監(jiān)聽RecyclerView的滾動,然后判斷是上拉加載更多的話析藕,則Adapter底部條目對應(yīng)顯示加載更多召廷,以及還可以擴展“沒有更多了”等效果。
1. 先看下Adapter的擴展處理吧 - 看關(guān)鍵點就可以了账胧!每個人的適配器不同竞慢!
1.1 首先就是如果是需要展示底部加載狀態(tài)的情況下,返回的item個數(shù)多一個
1.2 然后就是getItemViewType返回不同的類型
1.3 接著就是布局加載以及數(shù)據(jù)綁定治泥,小萌新是分別搞了多個不同狀態(tài)的布局筹煮。綁定數(shù)據(jù)的時候就處理正常條目數(shù)據(jù)即可,加載狀態(tài)的布局不做綁定操作>蛹小寺谤!
1.4 額外提供一些數(shù)據(jù)刷新以及狀態(tài)判斷的方法
/**
* 添加更多數(shù)據(jù)
* @param _baseMulDataModelList
*/
public void addMoreItem(List<BaseDataModel> _baseMulDataModelList) {
state = STATE.HIDE;
baseMulDataModelList.addAll(_baseMulDataModelList);
notifyDataSetChanged();
}
/**
* 開始加載
*/
public void startLoad() {
state = STATE.START_LOAD;
notifyDataSetChanged();
}
/**
* 加載中
*/
public void startLoading() {
state = STATE.LOADING;
notifyDataSetChanged();
}
/**
* 加載結(jié)束,無更多數(shù)據(jù)了
*/
public void finishNoMoreData() {
state = STATE.NO_MOREDATA;
notifyDataSetChanged();
}
/**
* 是否開始加載了
* @return
*/
public boolean bIsStart(){
return state == STATE.START_LOAD;
}
/**
* 是否正在加載
* @return
*/
public boolean bIsLoading(){
return state == STATE.LOADING || state == STATE.NO_MOREDATA;
}
public boolean bJustLoading(){
return state == STATE.LOADING;
}
2. 以上就基本構(gòu)建好了吮播。接下來就是我們適配器創(chuàng)建RecyclerView.OnScrollListener | Android Developers 变屁,上拉加載處理了...
2.1 你網(wǎng)上搜有些“SwipeRefreshLayout和RecyclerView實現(xiàn)下拉刷新上拉加載更多”,基本方案都類似意狠。就是細節(jié)處理不太一樣粟关。有些是在onScrollStateChanged中請求上拉加載,有些是在onScrolled處理上拉加載环戈,大部分還是在onScrollStateChanged中進行了處理闷板。
2.2 需要注意, 如果你的條目不滿一屏的話院塞,上拉加載不會觸發(fā)onScrolled回調(diào)遮晚,所以你要注意在onScrolled處理的地方喲!
按小萌新想法拦止,這種情況可以不用觸發(fā)上拉加載更多县遣,本來就那么多數(shù)據(jù)糜颠,正常的邏輯!哈哈萧求。其兴。。
2.3 所以小萌新的總結(jié)是:onScrolled中進行判斷是否上拉加載更多夸政,并且滿足條件元旬,開啟加載;然后當滑動停止時守问, 在onScrollStateChanged中進行數(shù)據(jù)加載(需要判斷是否開啟了加載匀归,這樣可以避免不滿一屏也上拉的操作;同時還要判斷是否正在下拉刷新這些情況....)
看下兩個方法和一些屬性介紹...
onScrollStateChanged
added in version 22.1.0
void onScrollStateChanged (RecyclerView recyclerView,
int newState)
Callback method to be invoked when RecyclerView's scroll state changes.
Parameters
recyclerView RecyclerView: The RecyclerView whose scroll state has changed.
newState int: The updated scroll state. One of SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING or SCROLL_STATE_SETTLING.
onScrolled
added in version 22.1.0
void onScrolled (RecyclerView recyclerView,
int dx,
int dy)
Callback method to be invoked when the RecyclerView has been scrolled. This will be called after the scroll has completed.
This callback will also be called if visible item range changes after a layout calculation. In that case, dx and dy will be 0.
Parameters
recyclerView RecyclerView: The RecyclerView which scrolled.
dx int: The amount of horizontal scroll.
dy int: The amount of vertical scroll.
dy可以用來判斷是下滑還是上滑的耗帕,目前按照我的邏輯朋譬,暫時不需要這個處理,我只需要判斷上拉加載更多時兴垦,是否達到了底部?- 靠獲取底部條目的bottom位置字柠,然后跟RecyclerView做差值探越,小于10基本就是了。此時就可以開始加載...
/**初始化界面***/
private void initView(){
///< 上拉加載更多監(jiān)聽
adh_dailyHisItemsRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
Log.e("test", "newState=" + newState);
///< 正在刷新窑业,則直接返回
if (dailyHisItemsSwr.isRefreshing() || baseAdapter.bIsLoading()) return;
///< 加載更多 onScrolled中上拉加載條件滿足時進行加載更多的操作??其他優(yōu)化钦幔?
if (baseAdapter.bIsStart() &&
!baseAdapter.bIsLoading() &&
newState == RecyclerView.SCROLL_STATE_IDLE) {
baseAdapter.startLoading();
loadMoreDate();
}
//Log.e("test", "startload=" + startload + " newState=" + newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//lastVisibleItem = layoutManager.findLastVisibleItemPosition();
//bUpLoad = dy > 0;
Log.e("test", "dy=" + dy);
if (baseAdapter.bIsLoading()) return;
View viewlast = layoutManager.findViewByPosition(baseAdapter.getItemCount() - 1);
Log.e("test", "viewlast=" + viewlast);
///< 當滑動到底部item的時候(此時底部bottom基本就是高度),設(shè)置startload = true常柄,展示加載中(可以修改為動畫顯示)
if (null != viewlast && (recyclerView.getHeight() - viewlast.getBottom()) < 10) {
//Log.e("test", "getHeight=" + recyclerView.getHeight());
//Log.e("test", "getTop=" + viewlast.getTop());
//Log.e("test", "getBottom=" + viewlast.getBottom());
baseAdapter.startLoad();
}
}
});
}
/**
* 加載更多
*/
private void loadMoreDate() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
List<BaseDataModel> mListTemp = new ArrayList<>();
for (int i = 0; i < 20; i++) {
DailyHisAdapterItemBean dailyHisAdapterItemBean = new DailyHisAdapterItemBean();
dailyHisAdapterItemBean.setDate("2019.11.11");
dailyHisAdapterItemBean.setNames("殺豬刀一把/青椒炒蘋果/蘿卜抄西瓜/蘿卜抄西瓜");
dailyHisAdapterItemBean.setTotal_price(12.28);
dailyHisAdapterItemBean.setTotal_weight(0.5);
mListTemp.add(dailyHisAdapterItemBean);
}
baseAdapter.addMoreItem(mListTemp);
baseAdapter.finishNoMoreData();
}
}, 2000);
}
基本就ojbk了鲤氢。。西潘。
總之卷玉,小萌新又接觸了一些知識。這塊后面要加強喷市,是需要看下第三方框架源碼相种,然后學習下。 雖然不用總是造輪子品姓,但是還是知道多點比較好吧....
心情好寝并,放松,開心的學習就好 - 小萌新