點滴RecyclerView

1.爲RecyclerView添加分割線

RecyclerView添加分割線可以在item的佈局中設置,這裏通過實現ItemDecoration來實現分割線效果。
思路:
a.創建一個類繼承RecyclerView.ItemDecoration,主要重寫兩個方法getItemOffsets(..)和onDraw(…),
b.getItemOffsets(…)確定的是item四邊的偏移量,onDraw(…)就是繪製分割線

/**
 * RecyclerView的LinearLayoutManager分割線
 */

public class RecyclerViewLinearDivider extends RecyclerView.ItemDecoration {
    private Drawable mDivider;
    private int mDividerHeight = 2;//分割線高度,默認是2px
    private int mOrientation;//列表方向:LinearLayoutManager.VERTICAL 或LinearLayoutManager.Horienzontal
    private int mDividerHeaderMargin;
    private int mDividerFooterMargin;

    /**
     * 自定義分割線
     *
     * @param context
     * @param orientation  列表方向
     * @param drawableId   分割線drawable資源
     * @param headerMargin 分割線距離頭的距離 單位:像素
     * @param footerMargin 分割線距離尾的距離 單位:像素
     */
    public RecyclerViewLinearDivider(Context context, int orientation, @DrawableRes int drawableId, int headerMargin, int footerMargin) {
        if (orientation != LinearLayoutManager.HORIZONTAL && orientation != LinearLayoutManager.VERTICAL) {
            throw new IllegalArgumentException("orientation must be LinearLayoutManager.HORIZONTAL or LinearLayoutManager.VERTICAL");
        }
        mOrientation = orientation;
        mDivider = ContextCompat.getDrawable(context, drawableId);
        mDividerHeight = Math.abs(mDivider.getIntrinsicHeight());//返回drawable的內在高度。
        mDividerHeaderMargin = headerMargin;
        mDividerFooterMargin = footerMargin;

    }


    //設置四個方向的偏移量,根據位置索引可以指定view的偏移量。這裏是當時畫橫線時,bottom的偏移量就是分割線的高度,也就是上下item的間隔,當畫縱線時,right的偏移量就是分割線的厚度,也就是左右item的間隔
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView recyclerView, RecyclerView.State state) {
        int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
        if (mOrientation == LinearLayoutManager.VERTICAL) {

            outRect.set(0, 0, mDividerHeight, 0);
        } else {
            outRect.set(0, 0, 0, mDividerHeight);
        }
    }

    //繪製分割線
    @Override
    public void onDraw(Canvas c, RecyclerView recyclerView, RecyclerView.State state) {
        if (mOrientation == LinearLayoutManager.VERTICAL) {
            drawVertical(c, recyclerView);
        } else {
            drawHorizontal(c, recyclerView);
        }
    }

    //繪製橫向item分割線
    private void drawHorizontal(Canvas c, RecyclerView recyclerView) {
        int left = recyclerView.getPaddingLeft();
        int right = recyclerView.getMeasuredWidth() - recyclerView.getPaddingRight();
        int childSize = recyclerView.getChildCount();
        for (int i = 0; i < childSize; i++) {
            View child = recyclerView.getChildAt(i);
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
            int top = child.getBottom() + layoutParams.bottomMargin;
            int bottom = top + mDividerHeight;
            if (mDivider != null) {
                mDivider.setBounds(left + mDividerHeaderMargin, top, right - mDividerFooterMargin, bottom);
                mDivider.draw(c);
            }
        }
    }

    //繪製縱向item分割線
    private void drawVertical(Canvas canvas, RecyclerView recyclerView) {
        int top = recyclerView.getPaddingTop();
        int bottom = recyclerView.getMeasuredHeight() - recyclerView.getPaddingBottom();
        int childSize = recyclerView.getChildCount();
        for (int i = 0; i < childSize; i++) {
            View child = recyclerView.getChildAt(i);
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
            int left = child.getRight() + layoutParams.rightMargin;
            int right = left + mDividerHeight;
            if (mDivider != null) {
                mDivider.setBounds(left, top + mDividerHeaderMargin, right, bottom - mDividerFooterMargin);
                mDivider.draw(canvas);
            }
        }
    }
}
/**
 * RecyclerView的GridLayoutManager的分割線
 */

public class RecyclerViewGridDivider extends RecyclerView.ItemDecoration {
    private static final String TAG = RecyclerViewGridDivider.class.getName();
    private Drawable mDivider;
    private int mDividerHeight = 2;//分割線高度,默認是2px

    public RecyclerViewGridDivider(Context context, @DrawableRes int drawableId) {
        mDivider = ContextCompat.getDrawable(context, drawableId);
        mDividerHeight = Math.abs(mDivider.getIntrinsicHeight());//返回drawable的內在高度。
    }


    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (((GridLayoutManager) parent.getLayoutManager()).getOrientation() != GridLayoutManager.VERTICAL) {
            throw new RuntimeException("GridLayoutManager's oritention must be GridLayoutManager.VERTICAL");
        }
        int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
        int spanCount = getSpanCount(parent);//列數
        int childCount = parent.getAdapter().getItemCount();


        int left = 0;
        @SuppressWarnings("SuspiciousNameCombination")
        int right = mDividerHeight;
        int top ;
        int bottom = mDividerHeight;

        if (isfirstRow(parent, itemPosition, spanCount, childCount)) {//如果是第一行,這裏做的是第一行距離頂端是分割線的高度
            top = mDividerHeight;
        } else {
            top = 0;
        }
        if (isLastRow(parent, itemPosition, spanCount, childCount)) {//如果是最後一行,item底端的偏移量置爲0,不再繪製分割線
            bottom = 0;
        }
        if (isRightMost(parent, itemPosition, spanCount, childCount)) {//如果是最右邊的一列,item的右邊偏移量置爲0,不再繪製分割線
            right = 0;
        }
        outRect.set(left, top, right, bottom);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        draw(c, parent);
    }

    //繪製橫向 item 分割線
    private void draw(Canvas canvas, RecyclerView parent) {
        int childSize = parent.getChildCount();
        for (int i = 0; i < childSize; i++) {
            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
            int left;
            int right;
            int top;
            int bottom;
            //畫水平分隔線
            left = child.getLeft();
            right = child.getRight();
            top = child.getBottom() + layoutParams.bottomMargin;
            bottom = top + mDividerHeight;
            if (mDivider != null) {
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(canvas);
            }
            //畫垂直分割線
            top = child.getTop();
            bottom = child.getBottom() + mDividerHeight;
            left = child.getRight() + layoutParams.rightMargin;
            right = left + mDividerHeight;
            if (mDivider != null) {
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(canvas);
            }
        }
    }


    /**
     * 判斷是否是最後一行
     *
     * @param parent
     * @param pos
     * @param spanCount
     * @param childCount
     * @return
     */
    private boolean isLastRow(RecyclerView parent, int pos, int spanCount, int childCount) {
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            int lines = childCount % spanCount == 0 ? childCount / spanCount : childCount / spanCount + 1;
            return lines == pos / spanCount + 1;
        } else {
            throw new RuntimeException("LayoutManager must be GridLayoutManager");
        }

    }

    /**
     * 判斷是否是第一行
     *
     * @param parent
     * @param pos
     * @param spanCount
     * @param childCount
     * @return
     */
    private boolean isfirstRow(RecyclerView parent, int pos, int spanCount, int childCount) {
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            int lines = childCount % spanCount == 0 ? childCount / spanCount : childCount / spanCount + 1;//得到有多少行
            if ((pos / spanCount) == 0) {//只有第一行的position<spanCount,其比值的int值一定是0
                return true;
            } else {
                return false;
            }
        } else {
            throw new RuntimeException("LayoutManager must be GridLayoutManager");
        }
    }

    /**
     * 是否是最右邊的一列
     *
     * @param parent
     * @param pos
     * @param spanCount
     * @param childCount
     * @return
     */
    private boolean isRightMost(RecyclerView parent, int pos, int spanCount, int childCount) {
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {

            if ((pos % spanCount) == (spanCount - 1)) {
                return true;
            } else {
                return false;
            }
        } else {
            throw new RuntimeException("LayoutManager must be GridLayoutManager");
        }
    }

    /**
     * 獲取列數
     *
     * @param parent
     * @return
     */
    private int getSpanCount(RecyclerView parent) {
        int spanCount = -1;
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
        } else {
            throw new RuntimeException("LayoutManager must be GridLayoutManager");
        }
        return spanCount;
    }
}

2.RecyclerView條目點擊事件的優化

RecyclerView點擊事件很多的做法就是對item每一個都設置點擊監聽,明顯是很消耗性能的方式,這裏通過觸摸監聽來實現點擊事件的監聽,進行優化,adapter只負責數據的綁定
思路:
1.爲RecyclerView添加手勢觸摸的監聽
2.重寫點擊事件的處理
3.OnRecyclerItemClickListener將別點擊的View的id和ViewHolder對象傳遞給RecyclerView,然後根據業務做處理

這裏寫圖片描述

OnGestureListener主要回調各種單擊事件,必須實現所有方法
OnDoubleTapListener主要回調各種雙擊事件,必須實現所有方法
SimpeOnGestureListener實現了上面兩個接口,都是空方法

OnGestureListener:
//用戶按下屏幕就會觸發
public boolean onDown(MotionEvent e);
//如果是按下的時間超過瞬間,而且在按下的時候沒有鬆開或者是拖動的,那麼onShowPress就會執行
public void onShowPress(MotionEvent e);
//一次單獨的輕擊擡起操作,也就是輕擊一下屏幕,就是普通點擊事件
public boolean onSingleTapUp(MotionEvent e);
//在屏幕上拖動事件
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
//長按觸摸屏,超過一定時長,就會觸發這個事件
public void onLongPress(MotionEvent e);
//滑屏,用戶按下觸摸屏、快速移動後鬆開
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);

OnDoubleTapListener:
//單擊事件。用來判定該次點擊是SingleTap而不是DoubleTap,
//如果連續點擊兩次就是DoubleTap手勢,如果只點擊一次,
//系統等待一段時間後沒有收到第二次點擊則判定該次點擊爲SingleTap而不是DoubleTap,
//然後觸發SingleTapConfirmed事件
public boolean onSingleTapConfirmed(MotionEvent e);
//雙擊事件
public boolean onDoubleTap(MotionEvent e);
//雙擊間隔中發生的動作。指觸發onDoubleTap以後,在雙擊之間發生的其它動作
public boolean onDoubleTapEvent(MotionEvent e);
/**
 * 實例化手勢探測器,需要一個手勢監聽器:OnGestureListener,
 * OnGestureListener主要回調各種單擊事件,必須實現所有方法
 * OnDoubleTapListener主要回調各種雙擊事件,必須實現所有方法
 * SimpeOnGestureListener實現了上面兩個接口,都是空方法
 */

public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
    private static final String TAG = OnRecyclerItemClickListener.class.getName();
    private GestureDetectorCompat mGestureDetector;
    private RecyclerView recyclerView;

    public OnRecyclerItemClickListener(RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
        mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener());
    }

    public abstract void onItemClick(int viewId, RecyclerView.ViewHolder viewHolder);

    public abstract void onItemLongClick(RecyclerView.ViewHolder vh);

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        mGestureDetector.onTouchEvent(e);//解析座標點和觸摸規律來識別觸摸手勢
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        mGestureDetector.onTouchEvent(e);//解析座標點和觸摸規律來識別觸摸手勢
    }

    @Override//用來處理觸摸衝突
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    }

    private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override//點擊事件處理
        public boolean onSingleTapUp(MotionEvent e) {
            final View itemRecycler = recyclerView.findChildViewUnder(e.getX(), e.getY());//獲取被點擊item的view
            if (itemRecycler instanceof ViewGroup) {
                ViewGroup itemView = (ViewGroup) itemRecycler;
                for (int i = 0; i < itemView.getChildCount(); i++) {
                    itemView.getChildAt(i).setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            onItemClick(v.getId(), recyclerView.getChildViewHolder(itemRecycler));

                        }
                    });
                }
            } else {
                itemRecycler.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (itemRecycler != null) {
                            onItemClick(itemRecycler.getId(), recyclerView.getChildViewHolder(itemRecycler));
                        }
                    }
                });
            }
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
            if (child != null) {
                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
                onItemLongClick(vh);
            }
        }
    }
}
mRecycler.addOnItemTouchListener(new OnRecyclerItemClickListener(mRecycler) {
             @Override
            public void onItemClick(int viewId, RecyclerView.ViewHolder viewHolder) {
                int position = viewHolder.getAdapterPosition();
                MyAdapter.ViewHolder myViewHolder = (MyAdapter.ViewHolder) viewHolder;
              switch (viewId){
                  case R.id.tv:
                      Toast.makeText(MainActivity.this,"第"+position+"個條目的TextView被點擊"+myViewHolder.tv.getText(),Toast.LENGTH_SHORT).show();
                      break;
                  case R.id.img:
                      Toast.makeText(MainActivity.this,"第"+position+"個條目的ImageView被點擊",Toast.LENGTH_SHORT).show();
                      break;
              }
            }
            @Override
            public void onItemLongClick(RecyclerView.ViewHolder vh) {
            }
        });

3.RecyclerView實現拖拽效果

這裏寫圖片描述     這裏寫圖片描述

/**
 * RecyclerView實現拖拽的幫助類,(swipe方式側滑刪除與拖拽同時存在時,會有衝突,這裏只有拖拽)
 */

public class RecyclerViewDragHelper extends ItemTouchHelper.Callback {
    private ItemTouchAdapter mItemTouchAdapter;
    private OnDragListener mOnDragListener;

    public RecyclerViewDragHelper(ItemTouchAdapter itemTouchAdapter) {
        this.mItemTouchAdapter = itemTouchAdapter;
    }

    @Override//設置recyclerview是否支持長按拖拽,默認是true,如果這裏返回true,所有的Item都支持拖拽,如果有特定的item不支持拖拽,這裏就要返回false,在長按的狀態下進行處理
    public boolean isLongPressDragEnabled() {
        return false;
    }

    @Override//設置item是否可以側滑刪除
    public boolean isItemViewSwipeEnabled() {
        return true;
    }

    @Override//設置是否處理拖拽事件和滑動事件以及拖拽和滑動操作的方向
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager){
            int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;//拖拽標誌
            int swipeFlags = 0;//滑動標誌,設置爲0,不處理滑動操作
            return makeMovementFlags(dragFlags,swipeFlags);
        }else if (layoutManager instanceof LinearLayoutManager){
            int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            int swipeFlags = 0 ;//列表可以實現側滑刪除,這裏用0 處理,側滑刪除和拖拽都需要長時間按住,所以手勢識別有衝突
            return makeMovementFlags(dragFlags,swipeFlags);
        }else {
            throw new IllegalArgumentException("LayoutManager must be GridLayoutManager or LinearLayoutManager");
        }
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        int fromPosition = viewHolder.getAdapterPosition();//獲取拖動viewholder的position
        int toPosition = target.getAdapterPosition();//獲取目標viewholder的position
        mItemTouchAdapter.onMove(fromPosition,toPosition);
        return true;
    }

    @Override//側滑刪除時調用,這裏沒有使用
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        int position = viewHolder.getAdapterPosition();
        mItemTouchAdapter.onSwiped(position);
    }

    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE){
            float alpha = 1 - Math.abs(dX)/(float)viewHolder.itemView.getWidth();
            viewHolder.itemView.setAlpha(alpha);
            viewHolder.itemView.setTranslationX(dX);
        }else {
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
        }
    }

    @Override//當長按選中item的時候調用(拖拽的時候)
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (actionState == ItemTouchHelper.ACTION_STATE_DRAG ){
            viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(R.color.md_red_A400));
        }
    }

    @Override//當手指鬆開時調用
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(R.color.md_red_A200));
        if (mOnDragListener != null) mOnDragListener.onFinishDrag();
    }

    public RecyclerViewDragHelper setOnDragListener(OnDragListener onDragListener){
        this.mOnDragListener = onDragListener;
        return this;
    }

    public  interface OnDragListener{
        void onFinishDrag();
    }

    public interface ItemTouchAdapter{
        void onMove(int fromPosition,int toPosition);//拖拽
        void onSwiped(int position);//側滑刪除
    }
}
  //Adapter要實現拖拽幫助類的接口,拖拽的接口,
  private class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements RecyclerViewDragHelper.ItemTouchAdapter{
  ...
   @Override
          public void onMove(int fromPosition, int toPosition) {
              if (fromPosition == mList.size() -1 || 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);
                  }
              }
              notifyItemMoved(fromPosition,toPosition);
          }
   ...
  }
//RecyclerView的監聽處理
 mRecycler.addOnItemTouchListener(new OnRecyclerItemClickListener(mRecycler) {
            @Override
            public void onItemClick(RecyclerView.ViewHolder vh) {
                int position = vh.getLayoutPosition();
                Toast.makeText(MainActivity.this,"第"+position+"被點擊了",Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemLongClick(RecyclerView.ViewHolder vh) {
                int childCount = ((RecyclerView)vh.itemView.getParent()).getAdapter().getItemCount();
                if (vh.getLayoutPosition() != childCount -1){//最後一個不能拖動
                    mDragHelper.startDrag(vh);//可以拖動
                    VibratorUtil.vibrate(MainActivity.this,70);
                }
            }
        });
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章