編輯:關於Android編程
經過再三思考,同時也為了大家使用方便,LRecyclerView集成了SwipeMenu系列功能,包括Item側滑菜單、長按拖拽Item,滑動刪除Item等功能。
demo apk下載地址:點我下載
本次新增SwipeMenu系列功能描述如下:
左右兩側都有菜單; 根據ViewType顯示菜單; 長按拖拽Item(List),與菜單結合; 長按拖拽Item(Grid); 滑動刪除Item; 指定某個Item不能拖拽或者不能滑動刪除; 用SwipeMenuLayout實現你自己的側滑。項目地址:https://github.com/jdsjlzx/LRecyclerView
為了實現SwipeMenu的功能,此次新增了一個SwipeMenuAdapter類。
SwipeMenuAdapter與library中已經存在的LRecyclerViewAdapter會不會沖突呢?答案是不會。SwipeMenuAdapter是用戶級別的基類adapter,也就是用戶需要繼承SwipeMenuAdapter去實現自己的adapter,還像之前那樣使用即可。
SwipeMenuAdapter類的定義:
public abstract class SwipeMenuAdapterextends RecyclerView.Adapter
實現自己的MenuAdapter:
public class MenuAdapter extends SwipeMenuAdapter{ protected List mDataList = new ArrayList<>(); public MenuAdapter() { } @Override public int getItemCount() { return mDataList.size(); } public List getDataList() { return mDataList; } public void setDataList(Collection list) { this.mDataList.clear(); this.mDataList.addAll(list); notifyDataSetChanged(); } public void addAll(Collection list) { int lastIndex = this.mDataList.size(); if (this.mDataList.addAll(list)) { notifyItemRangeInserted(lastIndex, list.size()); } } public void remove(int position) { mDataList.remove(position); notifyItemRemoved(position); if(position != mDataList.size()){ // 如果移除的是最後一個,忽略 notifyItemRangeChanged(position, mDataList.size() - position); } } public void clear() { mDataList.clear(); notifyDataSetChanged(); } @Override public View onCreateContentView(ViewGroup parent, int viewType) { return LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_text_swipe, parent, false); } @Override public MenuAdapter.DefaultViewHolder onCompatCreateViewHolder(View realContentView, int viewType) { return new DefaultViewHolder(realContentView); } @Override public void onBindViewHolder(MenuAdapter.DefaultViewHolder holder, int position) { String item = mDataList.get(position).title; DefaultViewHolder viewHolder = holder; viewHolder.tvTitle.setText(item); } static class DefaultViewHolder extends RecyclerView.ViewHolder { TextView tvTitle; public DefaultViewHolder(View itemView) { super(itemView); tvTitle = (TextView) itemView.findViewById(R.id.tv_title); } } }
是不是很方便?MenuAdapter基本的功能都滿足了,直接拷貝到項目中即可使用。
上面說了那麼多,關鍵的也就這幾句:
mDataAdapter = new MenuAdapter(); mDataAdapter.setDataList(dataList); mLRecyclerViewAdapter = new LRecyclerViewAdapter(this, mDataAdapter); mRecyclerView.setAdapter(mLRecyclerViewAdapter);
下面具體分析每個功能。
效果圖:
具體使用步驟如下。
為SwipeRecyclerView的Item創建菜單// 設置菜單創建器。 mRecyclerView.setSwipeMenuCreator(swipeMenuCreator); //設置菜單Item點擊監聽事件 mRecyclerView.setSwipeMenuItemClickListener(menuItemClickListener);
其中swipeMenuCreator和menuItemClickListener代碼如下:
/** * 菜單創建器。在Item要創建菜單的時候調用。 */ private SwipeMenuCreator swipeMenuCreator = new SwipeMenuCreator() { @Override public void onCreateMenu(SwipeMenu swipeLeftMenu, SwipeMenu swipeRightMenu, int viewType) { int size = getResources().getDimensionPixelSize(R.dimen.item_height); // 添加左側的,如果不添加,則左側不會出現菜單。 { SwipeMenuItem addItem = new SwipeMenuItem(mContext) .setBackgroundDrawable(R.drawable.selector_green)// 點擊的背景。 .setImage(R.mipmap.ic_action_add) // 圖標。 .setWidth(size) // 寬度。 .setHeight(size); // 高度。 swipeLeftMenu.addMenuItem(addItem); // 添加一個按鈕到左側菜單。 SwipeMenuItem closeItem = new SwipeMenuItem(mContext) .setBackgroundDrawable(R.drawable.selector_red) .setImage(R.mipmap.ic_action_close) .setWidth(size) .setHeight(size); swipeLeftMenu.addMenuItem(closeItem); // 添加一個按鈕到左側菜單。 } // 添加右側的,如果不添加,則右側不會出現菜單。 { SwipeMenuItem deleteItem = new SwipeMenuItem(mContext) .setBackgroundDrawable(R.drawable.selector_red) .setImage(R.mipmap.ic_action_delete) .setText("刪除") // 文字,還可以設置文字顏色,大小等。。 .setTextColor(Color.WHITE) .setWidth(size) .setHeight(size); swipeRightMenu.addMenuItem(deleteItem);// 添加一個按鈕到右側側菜單。 SwipeMenuItem closeItem = new SwipeMenuItem(mContext) .setBackgroundDrawable(R.drawable.selector_purple) .setImage(R.mipmap.ic_action_close) .setWidth(size) .setHeight(size); swipeRightMenu.addMenuItem(closeItem); // 添加一個按鈕到右側菜單。 SwipeMenuItem addItem = new SwipeMenuItem(mContext) .setBackgroundDrawable(R.drawable.selector_green) .setText("添加") .setTextColor(Color.WHITE) .setWidth(size) .setHeight(size); swipeRightMenu.addMenuItem(addItem); // 添加一個按鈕到右側菜單。 } } }; /** * 菜單點擊監聽。 */ private OnSwipeMenuItemClickListener menuItemClickListener = new OnSwipeMenuItemClickListener() { /** * Item的菜單被點擊的時候調用。 * @param closeable closeable. 用來關閉菜單。 * @param adapterPosition adapterPosition. 這個菜單所在的item在Adapter中position。 * @param menuPosition menuPosition. 這個菜單的position。比如你為某個Item創建了2個MenuItem,那麼這個position可能是是 0、1, * @param direction 如果是左側菜單,值是:SwipeMenuRecyclerView#LEFT_DIRECTION,如果是右側菜單,值是:SwipeMenuRecyclerView#RIGHT_DIRECTION. */ @Override public void onItemClick(Closeable closeable, int adapterPosition, int menuPosition, int direction) { closeable.smoothCloseMenu();// 關閉被點擊的菜單。 if (direction == LRecyclerView.RIGHT_DIRECTION) { Toast.makeText(mContext, "list第" + adapterPosition + "; 右側菜單第" + menuPosition, Toast.LENGTH_SHORT).show(); } else if (direction == LRecyclerView.LEFT_DIRECTION) { Toast.makeText(mContext, "list第" + adapterPosition + "; 左側菜單第" + menuPosition, Toast.LENGTH_SHORT).show(); } } };
從上面代碼可以看出,swipeMenuCreator完成了左右菜單的創建,menuItemClickListener實現了菜單的點擊事件。
需要注意的是,LRecyclerView提供了下面兩個方法,具體使用請詳見demo。
public void openLeftMenu(int position, int duration) { openMenu(position, LEFT_DIRECTION, duration); } public void openRightMenu(int position) { openMenu(position, RIGHT_DIRECTION, SwipeMenuLayout.DEFAULT_SCROLLER_DURATION); }
openLeftMenu:打開item的左邊菜單
openRightMenu:打開item的右邊菜單
這裡關鍵的就是這個position(詳細請參考demo),先埋下個伏筆,後面介紹。
效果圖:
根據ViewType決定SwipeMenu在哪一行出現,可以左側,可以右側。
自定義MenuViewTypeAdapter,代碼如下:
public class MenuViewTypeAdapter extends MenuAdapter { public static final int VIEW_TYPE_MENU = 1; public static final int VIEW_TYPE_NONE = 2; @Override public int getItemViewType(int position) { return position % 2 == 0 ? VIEW_TYPE_MENU : VIEW_TYPE_NONE; } }
在實現swipeMenuCreator 時,需要根據ItemViewType值來決定是否創建左右菜單。
private SwipeMenuCreator swipeMenuCreator = new SwipeMenuCreator() { @Override public void onCreateMenu(SwipeMenu swipeLeftMenu, SwipeMenu swipeRightMenu, int viewType) { // 根據Adapter的ViewType來決定菜單的樣式、顏色等屬性、或者是否添加菜單。 if (viewType == MenuViewTypeAdapter.VIEW_TYPE_NONE) { // Do nothing. } else if (viewType == MenuViewTypeAdapter.VIEW_TYPE_MENU) { int size = getResources().getDimensionPixelSize(R.dimen.item_height); ...... } } };
效果圖:
關鍵代碼:
mRecyclerView.setLongPressDragEnabled(true);// 開啟拖拽功能 mRecyclerView.setOnItemMoveListener(onItemMoveListener);// 監聽拖拽,更新UI。
onItemMoveListener具體如下:
/** * 當Item移動的時候。 */ private OnItemMoveListener onItemMoveListener = new OnItemMoveListener() { @Override public boolean onItemMove(int fromPosition, int toPosition) { final int adjFromPosition = mLRecyclerViewAdapter.getAdapterPosition(true, fromPosition); final int adjToPosition = mLRecyclerViewAdapter.getAdapterPosition(true, toPosition); // 當Item被拖拽的時候。 Collections.swap(mDataAdapter.getDataList(), adjFromPosition, adjToPosition); //Be carefull in here! mLRecyclerViewAdapter.notifyItemMoved(fromPosition, toPosition); return true;// 返回true表示處理了,返回false表示你沒有處理。 } @Override public void onItemDismiss(int position) { // 當Item被滑動刪除掉的時候,在這裡是無效的,因為這裡沒有啟用這個功能。 // 使用Menu時就不用使用這個側滑刪除啦,兩個是沖突的。 } };
注意下面代碼:
final int adjFromPosition = mLRecyclerViewAdapter.getAdapterPosition(true, fromPosition); final int adjToPosition = mLRecyclerViewAdapter.getAdapterPosition(true, toPosition);
關於position的位置,為了大家使用方便,特在LRecyclerViewAdapter中提供了一個方法getAdapterPosition(boolean isCallback, int position)。
isCallback 含義:position是否接口回調中帶來的 position 含義:如果不是接口回調,就是用戶自己指定的position getAdapterPosition(boolean isCallback, int position)只用於非LRecyclerViewAdapter提供的接口。
舉例說明:
setOnItemMoveListener不是 LRecyclerViewAdapter自帶接口(也就是內部方法),需要調用getAdapterPosition方法獲得正確的position 如setOnItemClickLitener 是 LRecyclerViewAdapter自帶接口,接口裡面自帶了position,用戶就不必調用getAdapterPosition方法,直接使用就可以了。mLRecyclerViewAdapter.setOnItemClickLitener(new OnItemClickLitener() { @Override public void onItemClick(View view, int position) { String text = "Click position = " + position; } @Override public void onItemLongClick(View view, int position) { } });
效果圖:
與list功能一樣,只是布局不一樣。
效果圖:
注意:
滑動刪除和滑動菜單是互相沖突的,兩者只能出現一個。
關鍵代碼:
mRecyclerView.setLongPressDragEnabled(true); mRecyclerView.setItemViewSwipeEnabled(true);// 開啟滑動刪除 mRecyclerView.setOnItemMoveListener(onItemMoveListener);// 監聽拖拽,更新UI
按照配置就可以實現滑動刪除。
效果圖:
關鍵代碼:
mRecyclerView.setLongPressDragEnabled(true); mRecyclerView.setItemViewSwipeEnabled(true);// 開啟滑動刪除。 mRecyclerView.setOnItemMoveListener(onItemMoveListener);// 監聽拖拽,更新UI。 mRecyclerView.setOnItemMovementListener(onItemMovementListener);
其中,onItemMovementListener具體實現如下:
/** * 當Item被移動之前。 */ public static OnItemMovementListener onItemMovementListener = new OnItemMovementListener() { /** * 當Item在移動之前,獲取拖拽的方向。 * @param recyclerView {@link RecyclerView}. * @param targetViewHolder target ViewHolder. * @return */ @Override public int onDragFlags(RecyclerView recyclerView, RecyclerView.ViewHolder targetViewHolder) { // 我們讓第一個不能拖拽 if (targetViewHolder.getAdapterPosition() == 0) { return OnItemMovementListener.INVALID;// 返回無效的方向。 } RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof LinearLayoutManager) {// 如果是LinearLayoutManager。 LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; if (linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) {// 橫向的List。 return OnItemMovementListener.LEFT | OnItemMovementListener.RIGHT; // 只能左右拖拽。 } else {// 豎向的List。 return OnItemMovementListener.UP | OnItemMovementListener.DOWN; // 只能上下拖拽。 } } else if (layoutManager instanceof GridLayoutManager) {// 如果是Grid。 return OnItemMovementListener.LEFT | OnItemMovementListener.RIGHT | OnItemMovementListener.UP | OnItemMovementListener.DOWN; // 可以上下左右拖拽。 } return OnItemMovementListener.INVALID;// 返回無效的方向。 } @Override public int onSwipeFlags(RecyclerView recyclerView, RecyclerView.ViewHolder targetViewHolder) { // 我們讓第一個不能滑動刪除。 if (targetViewHolder.getAdapterPosition() == 0) { return OnItemMovementListener.INVALID;// 返回無效的方向。 } RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof LinearLayoutManager) {// 如果是LinearLayoutManager LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; if (linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) {// 橫向的List。 return OnItemMovementListener.UP | OnItemMovementListener.DOWN; // 只能上下滑動刪除。 } else {// 豎向的List。 return OnItemMovementListener.LEFT | OnItemMovementListener.RIGHT; // 只能左右滑動刪除。 } } return OnItemMovementListener.INVALID;// 其它均返回無效的方向。 } };
onItemMoveListener具體實現如下:
/** * 當Item移動的時候。 */ private OnItemMoveListener onItemMoveListener = new OnItemMoveListener() { @Override public boolean onItemMove(int fromPosition, int toPosition) { final int adjFromPosition = mLRecyclerViewAdapter.getAdapterPosition(true, fromPosition); final int adjToPosition = mLRecyclerViewAdapter.getAdapterPosition(true, toPosition); if (adjToPosition == 0) {// 保證第一個不被擠走。 return false; } // 當Item被拖拽的時候。 Collections.swap(mDataAdapter.getDataList(), adjFromPosition, adjToPosition); //Be carefull in here! mLRecyclerViewAdapter.notifyItemMoved(fromPosition, toPosition); return true; } @Override public void onItemDismiss(int position) { final int adjPosition = mLRecyclerViewAdapter.getAdapterPosition(true, position); mDataAdapter.remove(adjPosition); AppToast.showShortText(DragSwipeFlagsActivity.this, "現在的第" + adjPosition + "條被刪除。"); } };
通過代碼中的注釋,就可以明白了,一切盡在代碼中。
效果圖:
這個與LRecyclerView關系不大,但是與SwipeMenu關系密切。為了實現滑動菜單的功能,定義了SwipeMenuLayout。
SwipeMenuLayout類的定義:
public class SwipeMenuLayout extends FrameLayout implements SwipeSwitch
在開頭提到的SwipeMenuAdapter的
@Override public final VH onCreateViewHolder(ViewGroup parent, int viewType) { View contentView = onCreateContentView(parent, viewType); if (mSwipeMenuCreator != null) { SwipeMenuLayout swipeMenuLayout = (SwipeMenuLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_recyclerview_swipe_item_default, parent, false); ...... }
layout_recyclerview_swipe_item_default.xml
<framelayout android:id="@id/swipe_content" android:layout_height="wrap_content" android:layout_width="match_parent"> </framelayout>
看來這個布局,你是不是有種恍然大悟的感覺呢?左右滑動就是通過SwipeMenuView來實現的。
項目地址:https://github.com/jdsjlzx/LRecyclerView,歡迎Star!
我們在ListView中需要下載資源時,贊不考慮緩存機制,那麼每一個Item可能都需要開啟一個線程去下載資源(如果沒有線程池),如果Item很多,那麼我們可能就會無限制的
Activity在inflate layout時,通過DataBindingUtil來生成綁定,從代碼看,是遍歷contentView得到View數組對象,然後通過數據綁
前言 好久沒有雙休一個周末了,唉,閒話不多說了,不管任何時候都要多學習充實自己。ROM Porting的工作是很繁瑣的,涉及的知識點很多,這裡介紹一下ROM Portin
在Android應用開發中,滑動側邊欄經常使用,今天我也試著自己進行了一個簡單的實踐,雖然功能還不是很強大,但是可以保留下來為以後的開發使用,有需要時在進行簡單的修改。實