1. 程式人生 > >RecycleView懸浮頭部使用筆記

RecycleView懸浮頭部使用筆記

https://github.com/qdxxxx/StickyHeaderDecoration

1.因為頭部不是我想要的,所以直接下載程式碼後進行修改,程式碼:

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

import java.util.HashMap;
import java.util.Map;


public abstract class RecycleViewDecoration extends RecyclerView.ItemDecoration {
    protected String TAG = "RecycleViewDecoration";
    private Paint mHeaderTxtPaint;//文字
    private Paint mHeaderContentPaint;//背景
    private Paint mBottomLinePaint;//底部線條
    private Paint mBitmapPaint;//右邊箭頭

    protected int headerHeight = 136;//頭部高度
    private int textPaddingLeft = 30;//頭部文字左邊距
    private int textPaddingRght=40;//右邊文字邊距
    protected int bottomLineHeight=1;
    private int textSize = 50;
    private int textColor = Color.BLACK;
    private int headerContentColor = 0xffeeeeee;
    private int bottomLineColor=0xffeeeeee;
    private Bitmap bitmap;
    private int bitmapPadding=30;//箭頭右邊邊距

    private final float txtYAxis;
    private RecyclerView mRecyclerView;


    public RecycleViewDecoration() {
        mHeaderTxtPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHeaderTxtPaint.setColor(textColor);
        mHeaderTxtPaint.setTextSize(textSize);
        mHeaderTxtPaint.setTextAlign(Paint.Align.LEFT);


        mHeaderContentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHeaderContentPaint.setColor(headerContentColor);
        Paint.FontMetrics fontMetrics = mHeaderTxtPaint.getFontMetrics();
        float total = -fontMetrics.ascent + fontMetrics.descent;
        txtYAxis = total / 2 - fontMetrics.descent;

        mBitmapPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        bitmap= BitmapFactory.decodeResource(BaseApplication.getContext().getResources(),
                R.mipmap.ic_arrow_right);

        mBottomLinePaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        mBottomLinePaint.setColor(bottomLineColor);
    }
    private boolean isInitHeight = false;

    /**
     * 先呼叫getItemOffsets再呼叫onDraw
     */
    @Override
    public void getItemOffsets(Rect outRect, View itemView, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, itemView, parent, state);
        if (mRecyclerView == null) {
            mRecyclerView = parent;
        }

        if (headerDrawEvent != null && !isInitHeight) {
            View headerView = headerDrawEvent.getHeaderView(0);
            headerView
                    .measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            headerHeight = headerView.getMeasuredHeight();
            isInitHeight = true;
        }

        /*我們為每個不同頭部名稱的第一個item設定頭部高度*/
        int pos = parent.getChildAdapterPosition(itemView); //獲取當前itemView的位置
        String curHeaderName = getHeaderName(pos);         //根據pos獲取要懸浮的頭部名

        if (curHeaderName == null) {
            return;
        }
        if (pos == 0 || !curHeaderName.equals(getHeaderName(pos - 1))) {//如果當前位置為0,或者與上一個item頭部名不同的,都騰出頭部空間
            outRect.top = headerHeight;                                 //設定itemView PaddingTop的距離
        }
    }

    public abstract String getHeaderName(int pos);
    public abstract String getHeaderRightText(int pos);

    private SparseArray<Integer> stickyHeaderPosArray = new SparseArray<>();//記錄每個頭部和懸浮頭部的座標資訊【用於點選事件】
    private GestureDetector gestureDetector;


    @Override
    public void onDrawOver(Canvas canvas, RecyclerView recyclerView, RecyclerView.State state) {
        super.onDrawOver(canvas, recyclerView, state);
        if (mRecyclerView == null) {
            mRecyclerView = recyclerView;
        }
        if (gestureDetector == null) {
            gestureDetector = new GestureDetector(recyclerView.getContext(), gestureListener);
            recyclerView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    return gestureDetector.onTouchEvent(event);
                }
            });
        }

        stickyHeaderPosArray.clear();

        int childCount = recyclerView.getChildCount();//獲取螢幕上可見的item數量
        int left = recyclerView.getLeft() + recyclerView.getPaddingLeft();
        int right = recyclerView.getRight() - recyclerView.getPaddingRight();

        String firstHeaderName = null;
        String headerRirhtText=null;
        int firstPos = 0;
        int translateTop = 0;//繪製懸浮頭部的偏移量
        /*for迴圈裡面繪製每個分組的頭部*/
        for (int i = 0; i < childCount; i++) {
            View childView = recyclerView.getChildAt(i);
            int pos = recyclerView.getChildAdapterPosition(childView); //獲取當前view在Adapter裡的pos
            String curHeaderName = getHeaderName(pos);                 //根據pos獲取要懸浮的頭部名
            String curHeaderRightText=getHeaderRightText(pos);
            if (i == 0) {
                firstHeaderName = curHeaderName;
                headerRirhtText=curHeaderRightText;
                firstPos = pos;
            }
            if (curHeaderName == null)
                continue;//如果headerName為空,跳過此次迴圈

            int viewTop = childView.getTop() + recyclerView.getPaddingTop();
            if (pos == 0 || !curHeaderName.equals(getHeaderName(pos - 1))) {//如果當前位置為0,或者與上一個item頭部名不同的,都騰出頭部空間
                if (headerDrawEvent != null) {
                    View headerView;
                    if (headViewMap.get(pos) == null) {
                        headerView = headerDrawEvent.getHeaderView(pos);
                        headerView
                                .measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                                        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
                        headerView.setDrawingCacheEnabled(true);
                        headerView.layout(0, 0, right, headerHeight);//佈局layout
                        headViewMap.put(pos, headerView);
                        canvas.drawBitmap(headerView.getDrawingCache(), left, viewTop - headerHeight, null);

                    } else {
                        headerView = headViewMap.get(pos);
                        canvas.drawBitmap(headerView.getDrawingCache(), left, viewTop - headerHeight, null);
                    }
                } else {
                    canvas.drawRect(left, viewTop - headerHeight, right, viewTop, mHeaderContentPaint);
                    canvas.drawText(curHeaderName, left + textPaddingLeft, viewTop - headerHeight / 2 + txtYAxis, mHeaderTxtPaint);

                    if (!TextUtils.isEmpty(headerRirhtText)){
                        Rect mBounds = new Rect();
                        mHeaderTxtPaint.getTextBounds(headerRirhtText,0,headerRirhtText.length(),mBounds);
                        float rightTextWidth=mBounds.width();
                        canvas.drawText(headerRirhtText, right-rightTextWidth-bitmap.getWidth()-textPaddingRght, viewTop - headerHeight / 2 + txtYAxis, mHeaderTxtPaint);
                    }
                    canvas.drawBitmap(bitmap,right-bitmap.getWidth()-bitmapPadding,viewTop -headerHeight / 2 - bitmap.getHeight()/2,mBitmapPaint);
                    canvas.drawRect(left, viewTop, right, viewTop+bottomLineHeight, mBottomLinePaint);
                }
                if (headerHeight < viewTop && viewTop <= 2 * headerHeight) { //此判斷是剛好2個頭部碰撞,懸浮頭部就要偏移
                    translateTop = viewTop - 2 * headerHeight;
                }
                stickyHeaderPosArray.put(pos, viewTop);//將頭部資訊放進array
                Log.i(TAG, "繪製各個頭部" + pos);
            }
        }
        if (firstHeaderName == null)
            return;


        canvas.save();
        canvas.translate(0, translateTop);
        if (headerDrawEvent != null) {//inflater
            View headerView;
            if (headViewMap.get(firstPos) == null) {
                headerView = headerDrawEvent.getHeaderView(firstPos);
                headerView.measure(
                        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
                headerView.setDrawingCacheEnabled(true);
                headerView.layout(0, 0, right, headerHeight);//佈局layout
                headViewMap.put(firstPos, headerView);
                canvas.drawBitmap(headerView.getDrawingCache(), left, 0, null);
            } else {
                headerView = headViewMap.get(firstPos);
                canvas.drawBitmap(headerView.getDrawingCache(), left, 0, null);
            }
        } else {
            /*繪製懸浮的頭部*/
            canvas.drawRect(left, 0, right, headerHeight, mHeaderContentPaint);
            canvas.drawText(firstHeaderName, left + textPaddingLeft, headerHeight / 2 + txtYAxis, mHeaderTxtPaint);
            if (!TextUtils.isEmpty(headerRirhtText)){
                Rect mBounds = new Rect();
                mHeaderTxtPaint.getTextBounds(headerRirhtText,0,headerRirhtText.length(),mBounds);
                float rightTextWidth=mBounds.width();
                canvas.drawText(headerRirhtText, right-rightTextWidth-bitmap.getWidth()-textPaddingRght, headerHeight / 2 + txtYAxis, mHeaderTxtPaint);
            }
            canvas.drawBitmap(bitmap,right-bitmap.getWidth()-bitmapPadding,headerHeight / 2 - bitmap.getHeight()/2,mBitmapPaint);
            canvas.drawRect(left, headerHeight, right, headerHeight+bottomLineHeight, mBottomLinePaint);
        }
        canvas.restore();

        Log.i(TAG, "繪製懸浮頭部");
    }

    private Map<Integer, View> headViewMap = new HashMap<>();

    public interface OnHeaderClickListener {
        void headerClick(int pos);
    }

    private OnHeaderClickListener headerClickEvent;

    public void setOnHeaderClickListener(OnHeaderClickListener headerClickListener) {
        this.headerClickEvent = headerClickListener;
    }


    private GestureDetector.OnGestureListener gestureListener = new GestureDetector.OnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            for (int i = 0; i < stickyHeaderPosArray.size(); i++) {
                int value = stickyHeaderPosArray.valueAt(i);
                LogUtil.e("value"+value);
                float y = e.getY();
                if (value - headerHeight <= y && y <= value) {//如果點選到分組頭
                    if (headerClickEvent != null) {
                        headerClickEvent.headerClick(stickyHeaderPosArray.keyAt(i));
                        LogUtil.e("stickyHeaderPosArray.keyAt(i)"+stickyHeaderPosArray.keyAt(i));
                    }
                    return true;
                }
            }
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return false;
        }

        @Override
        public void onLongPress(MotionEvent e) {
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return false;
        }
    };

    private OnDecorationHeadDraw headerDrawEvent;

    public interface OnDecorationHeadDraw {
        View getHeaderView(int pos);
    }

    /**
     * 只是用來繪製,不能做其他處理/點選事件等
     */
    public void setOnDecorationHeadDraw(OnDecorationHeadDraw decorationHeadDraw) {
        this.headerDrawEvent = decorationHeadDraw;
    }


    private Map<String, Drawable> imgDrawableMap = new HashMap<>();

    private Drawable getImg(String url) {
        return imgDrawableMap.get(url);
    }

    public void onDestory() {
        headViewMap.clear();
        imgDrawableMap.clear();
        stickyHeaderPosArray.clear();
        mRecyclerView = null;
        setOnHeaderClickListener(null);
        setOnDecorationHeadDraw(null);
    }


    public void setHeaderHeight(int headerHeight) {
        this.headerHeight = headerHeight;
    }

    public void setTextPaddingLeft(int textPaddingLeft) {
        this.textPaddingLeft = textPaddingLeft;
    }

    public void setTextSize(int textSize) {
        this.textSize = textSize;
        this.mHeaderTxtPaint.setTextSize(textSize);
    }

    public void setTextColor(int textColor) {
        this.textColor = textColor;
        this.mHeaderTxtPaint.setColor(textColor);
    }

    public void setHeaderContentColor(int headerContentColor) {
        this.headerContentColor = headerContentColor;
        this.mHeaderContentPaint.setColor(headerContentColor);
    }

    public void setBottomLineHeight(int bottomLineHeight) {
        this.bottomLineHeight = bottomLineHeight;
    }

    public void setBottomLineColor(int bottomLineColor) {
        this.bottomLineColor = bottomLineColor;
        this.mBottomLinePaint.setColor(bottomLineColor);
    }
}

2.使用和原來的一樣,但是滑動後頭部點選沒反應

https://github.com/timehop/sticky-headers-recyclerview

這個很方便,懸浮頭部也可點選,但是點選返回不對,解決辦法:直接判斷position是否等於0,等於0就返回recycleView的當前顯示的第一個item的位置,這樣就可以正確獲取位置了

StickyRecyclerHeadersTouchListener touchListener =
        new StickyRecyclerHeadersTouchListener(recyclerView, headersDecor);
touchListener.setOnHeaderClickListener(
        new StickyRecyclerHeadersTouchListener.OnHeaderClickListener() {
            @Override
            public void onHeaderClick(View header, int position, long headerId) {
                int itemPosition;
                if (position==0){
                    itemPosition=manager.findFirstVisibleItemPosition();
                }else {
                    itemPosition=position;
                }
    
            }
        });
recyclerView.addOnItemTouchListener(touchListener);