Android StickHeaderRecyclerView - 讓recyclerview頭部固定


介紹
在項目中有時會需要recyclerview滑動式時某個view滑出后會固定在頭部顯示,比較常用的比如手機聯系人界面、地區選擇界面等。 StickHeaderRecyclerView就是實現這個功能的。效果圖:

這樣的控件網上一抓一大把了,本控件的優點就是使用簡單- lib簡單 - 使用的語法也簡單(之前下了2個類似開源項目,都是上萬行代碼。讀起來麻煩、改起來麻煩就自己寫了這個控件)

使用
只需要讓你的adapter實現StickHeaderDecoration.StickHeaderInterface接口,方法boolean isStick(int position)中返回的值就標識當前位置的view是否需要固定。
同時需要讓Adapter中的item不復用(如果怕影響性能也可以單獨讓需要固定的view不復用) 在adapter構造方法中setHasStableIds(true); 同時復寫adapter的public long getItemId(int position) {return position;}
上代碼

public class NormalAdapter  extends RecyclerView.Adapter<NormalAdapter.InnerHolder> implements StickHeaderDecoration.StickHeaderInterface{

    NormalAdapter(Activity activity, List<String> dates){
        this.activity = activity;
        this.dates = dates;
    }

    @Override
    public boolean isStick(int position) {
        return position % 6 == 0;
    }

    Activity activity;
    private List<String> dates;

    @Override
    public InnerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View inflate = LayoutInflater.from(activity).inflate(R.layout.item, parent,
                false);
        return new InnerHolder(inflate);
    }

    @Override
    public void onBindViewHolder(InnerHolder holder, int position) {
        if(isStick(position)){
            holder.itemView.setBackgroundResource(R.color.colorAccent);
            holder.tvText.setText(position / 6 +"");
        }else{
            holder.itemView.setBackgroundResource(R.color.white);
            holder.tvText.setText(dates.get(position));
        }
    }

    @Override
    public int getItemCount() {
        return dates.size();
    }

    class InnerHolder extends RecyclerView.ViewHolder{
        TextView tvText;
        public InnerHolder(View itemView) {
            super(itemView);
            tvText = (TextView) itemView.findViewById(R.id.tvText);
        }
    }
}

activity代碼

public class MainActivity extends Activity {

    private RecyclerView recycle;
    private List<String> dates = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recycle = (RecyclerView) findViewById(R.id.recycle);

        for(int i=0;i<66;i++){
            dates.add("date : "+i);
        }

        recycle.setLayoutManager(new LinearLayoutManager(this));
        recycle.setAdapter(new NormalAdapter(this, dates));
        recycle.addItemDecoration(new StickHeaderDecoration(recycle));
    }

}

完成了

原理

先上核心類代碼

public class StickHeaderDecoration extends RecyclerView.ItemDecoration {

    public interface StickHeaderInterface {
        /**
         * is this item need stick
         * @param position now item position in the recyclerView
         * @return true : need stick else not
         */
        boolean isStick(int position);
    }

    private RecyclerView recyclerView;
    private RecyclerView.LayoutManager manager;
    private RecyclerView.Adapter adapter;
    private StickHeaderInterface stickHeaderInterface;

    /**
     * 進行一些容錯檢查
     */
    public StickHeaderDecoration(RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
        this.manager = recyclerView.getLayoutManager();
        this.adapter = recyclerView.getAdapter();
        if (adapter == null) {
            throw new RuntimeException("please set Decoration after set adapter");
        }

        if (adapter instanceof StickHeaderInterface) {
            stickHeaderInterface = (StickHeaderInterface) adapter;
            return;
        }
        throw new RuntimeException("please make your adapter implements StickHeaderInterface");
    }

    /**
     * 繪制頭部的stick view
     */
    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        View childAt = parent.getChildAt(0);
        if (childAt == null)
            return;
        RecyclerView.ViewHolder childViewHolder = parent.getChildViewHolder(childAt);
        int position = childViewHolder.getPosition();
        for (int i = position; i >= 0; i--) {
            if (stickHeaderInterface.isStick(i)) {
                int top = 0;
                if (position + 1 < adapter.getItemCount()) {
                    if (stickHeaderInterface.isStick(position + 1)) {
                        View childNext = parent.getChildAt(1);
                        top = manager.getDecoratedTop(childNext) < 0 ? 0 : manager
                                .getDecoratedTop(childNext);
                    }
                }
                RecyclerView.ViewHolder inflate = recyclerView.getAdapter().createViewHolder(parent,
                        recyclerView.getAdapter().getItemViewType(i));
                recyclerView.getAdapter().bindViewHolder(inflate, i);
                int measureHeight = getMeasureHeight(inflate.itemView);
                c.save();
                if (top < inflate.itemView.getMeasuredHeight() && top > 0) {
                    c.translate(0, top - measureHeight);
                }
                inflate.itemView.draw(c);
                c.restore();
                return;
            }
        }
    }

    /**
     * 測量控件的高度
     *
     * @param header
     */
    private int getMeasureHeight(View header) {
        int widthSpec = View.MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), View
                .MeasureSpec.EXACTLY);
        int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        header.measure(widthSpec, heightSpec);
        header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
        return header.getMeasuredHeight();
    }
}

首先ItemDecoration是一個接口,通過RecyclerView的 recycle.addItemDecoration方法設置進去
其中只有6個方法其中3個過時了。我們這兒只需要對onDrawOver進行操作。
onDrawOver是當前RecyclerView繪制完畢后調用,可以其中進行繪制。我們的頭部固定其實就是在這個方法中繪制進去的。

算法
1.這兒我們需要判斷當前顯示item的前面是否有需要固定的item(這兒取名為beforitem)如果有則繪制在頂部
2.我們還需要當第二個固定的item把前面的item慢慢頂上去的效果,這兒通過判斷當前顯示的第一個item的下一個item是否需要固定,如果需要則通過manager.getDecoratedTop(childNext)獲取這個item距離頂部的距離然后通過計算把beforitem先上移動一定的距離。
基本原理就這樣,相信代碼更加有說服力,github 地址
https://github.com/LiuLinXin/StickHeaderRecyclerView-philer

待優化
頭部view現在是通過ondraw繪制進去的,不能相應點擊事件等。暫時沒相處好的解決辦法,希望有想法的朋友提示下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM