安卓作业----慕课移动应用开发作业13之使用自定义RecyclerView.ItemDecoration实现列表悬浮顶部效果

此博客通过RecyclerView、TextView等进行界面布局,使用自定义RecyclerView.Adapter、RecyclerViewAdapter.ViewHolder以及自定义RecyclerView.ItemDecoration实现分组列表以及悬浮顶部效果

同时这也是中国大学慕课移动终端应用开发的网课作业13,我会持续更新我的作业,如果有需要关注一下吧

说明

1.非常感谢此篇博文以及博文作者,详细介绍了RecyclerView.ItemDecoration的用法,让我少花了很多时间。
2.自定义RecyclerView.ItemDecoration,即下面代码部分的WordItemDecoration.java类,我补充了许多注释,希望大家看的更轻松
3.如果想了解有关ItemDecoration更多知识,请戳我第一点的链接
4.我在作业要求的基础上进行拓展,做了一个单词查阅目录,我觉得这样作品可能更实用一些。

效果图

在这里插入图片描述

代码部分

模型:Word.java
public class Word {
    private String initial;//此单词的首字母
    private String english;//单词英文
    private String chinese;//单词中文

    public Word(String english, String chinese) {
        this.english = english;
        this.chinese = chinese;
        this.initial = english.substring(0,1).toUpperCase();  //首字母获取
    }

    public String getInitial() {
        return initial;
    }

    public void setInitial(String initial) {
        this.initial = initial;
    }

    public String getEnglish() {
        return english;
    }

    public void setEnglish(String english) {
        this.english = english;
    }

    public String getChinese() {
        return chinese;
    }

    public void setChinese(String chinese) {
        this.chinese = chinese;
    }
}
自定义适配器:WordAdapter.java
public class WordAdapter extends RecyclerView.Adapter<WordAdapter.ViewHolder> {
    private Context mContext;//上下文对象
    private ArrayList<Word> mWords;
    private LayoutInflater mInflater;

    public WordAdapter(Context context, ArrayList<Word> words) {
        mContext = context;
        mWords = words;
        mInflater = LayoutInflater.from(mContext);
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.word_item, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
        Word word = mWords.get(position);
        holder.mTextViewWordEnglish.setText(word.getEnglish());
        holder.mTextViewWordChinese.setText("释义:"+word.getChinese());
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, "点击的单词是:"+ mWords.get(position).getEnglish()+",中文是:"+mWords.get(position).getChinese(), Toast.LENGTH_SHORT).show();
            }
        });
    }

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

    class ViewHolder extends RecyclerView.ViewHolder{
        public TextView mTextViewWordEnglish;
        public TextView mTextViewWordChinese;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            mTextViewWordEnglish = itemView.findViewById(R.id.word_english);
            mTextViewWordChinese = itemView.findViewById(R.id.word_chinese);

        }
    }
}
自定义修饰:WordItemDecoration.java
public class WordItemDecoration extends RecyclerView.ItemDecoration {
    private ArrayList<Word> mWords;//设置数据
    private Paint mPaint;//设置画悬浮栏的画笔
    private Rect mRectBounds;//设置一个矩形,用于画文字

    private int mTitleHeight;//设置悬浮栏的高度
    private int mTextSize;//设置文字大小
    private Context mContext;//设置上下文对象

    public WordItemDecoration(Context context,ArrayList<Word> words) {
        mWords = words;
        mContext = context;

        //设置悬浮栏高度以及文字大小,为了统一尺寸规格,转换为像素
        mTitleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 45, mContext.getResources().getDisplayMetrics());
        mTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 30, mContext.getResources().getDisplayMetrics());

        mRectBounds = new Rect();//初始化矩形
        //初始化画笔
        mPaint = new Paint();
        mPaint.setAntiAlias(true);//抗锯齿
        mPaint.setDither(true);//防抖动
        mPaint.setTextSize(mTextSize);
    }

    /**
     * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
     * Any content drawn by this method will be drawn before the item views are drawn,
     * and will thus appear underneath the views.
     *
     * 提供给RecyclerView的画布中绘制任何适当的装饰。
     * 使用此方法绘制的任何内容都将在绘制项目视图之前绘制,因此将显示在视图下面。
     *
     * @param c      Canvas to draw into    画布
     * @param parent RecyclerView this ItemDecoration is drawing into   正在使用装饰的recycle view
     * @param state  The current state of RecyclerView      即RecyclerView的当前状态
     */
    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        /**
         * 这个方法负责绘制每一个标题,可以实现随着视图移动而移动
         * */

        super.onDraw(c, parent, state);

        //先画出带有背景颜色的矩形条悬浮栏,从哪个位置开始绘制到哪个位置结束,则需要先确定位置,再画文字(即:title)
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();

        //父view(RecyclerView)有padding值,子view有margin值
        int childCount = parent.getChildCount();//得到的数据其实是一屏可见的item数量并非总item数,再复用
        for(int i = 0; i < childCount; i++){
            View child = parent.getChildAt(i);
            //子view(即:item)有可能设置有margin值,所以需要parms来设置margin值
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            //以及 获取 position 位置
            int position = params.getViewLayoutPosition();
            if(position > -1){
                if(position == 0){//肯定是要绘制一个悬浮栏 以及 悬浮栏内的文字
                    //画矩形悬浮条以及文字
                    drawRectAndText(c, left, right, child, params, position);
                }else{
                    if(mWords.get(position).getInitial() != null && !mWords.get(position).getInitial().equals(mWords.get(position - 1).getInitial())){
                        //和上一个Tag不一样,说明是另一个新的分组
                        //画矩形悬浮条以及文字
                        drawRectAndText(c, left, right, child, params, position);
                    }else{
                        //说明是一组的,什么也不画,共用同一个首字母
                    }
                }
            }

        }

    }

    /**
     * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
     * Any content drawn by this method will be drawn after the item views are drawn
     * and will thus appear over the views.
     *
     * 在提供给RecyclerView的画布中绘制任何适当的装饰。
     * 使用此方法绘制的任何内容都将在绘制项目视图之后绘制,因此将显示在视图上。
     *
     * @param c      Canvas to draw into
     * @param parent RecyclerView this ItemDecoration is drawing into
     * @param state  The current state of RecyclerView.
     */
    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        /**
         * 这个方法可以显示在视图上面,所以可以实现悬浮标题的效果
         * */
        super.onDrawOver(c, parent, state);


        //其实就是获取到每一个可见的位置的item时,执行画顶层悬浮栏
        int firstPosition = ((LinearLayoutManager)parent.getLayoutManager()).findFirstVisibleItemPosition();
        View child = parent.findViewHolderForLayoutPosition(firstPosition).itemView;
        //绘制悬浮栏,其实这里和上面onDraw()绘制方法差不多,只不过,这里面的绘制是在最上层,会悬浮
        mPaint.setColor(Color.parseColor("#C5E4FD"));
        c.drawRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + mTitleHeight, mPaint);
        //绘制文字
        mPaint.setColor(Color.parseColor("#555555"));
        mPaint.getTextBounds(mWords.get(firstPosition).getInitial(), 0, mWords.get(firstPosition).getInitial().length(), mRectBounds);
        c.drawText(mWords.get(firstPosition).getInitial(), child.getPaddingLeft()+40, parent.getPaddingTop() + mTitleHeight - (mTitleHeight/2 - mRectBounds.height()/2), mPaint);

    }

    /**
     * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
     * the number of pixels that the item view should be inset by, similar to padding or margin.
     * The default implementation sets the bounds of outRect to 0 and returns.
     *
     * 检索给定项的任何偏移量。outRect<code>outRect</code>的每个字段指定项目视图应插入的像素数,
     * 类似于填充或边距。默认实现将outRect的边界设置为0并返回
     *
     * <p>
     * If this ItemDecoration does not affect the positioning of item views, it should set
     * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
     * before returning.
     * 如果此ItemDecoration不影响项视图的位置,则在返回之前,
     * 它应将<code>outRect</code>的所有四个字段(左、上、右、下)设置为零。
     *
     * <p>
     * If you need to access Adapter for additional data, you can call
     * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
     * View.
     * 如果需要访问适配器以获取其他数据,
     * 可以调用{@link RecyclerView#getChildAdapterPosition(View)}获取查看。
     *
     *
     * @param outRect Rect to receive the output.
     * @param view    The child view to decorate
     * @param parent  RecyclerView this ItemDecoration is decorating
     * @param state   The current state of RecyclerView.
     */
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        /**
         * 这个方法设置预留空间
         * */
        super.getItemOffsets(outRect, view, parent, state);

        //获取position,由本方法的第三段注释可得
        int position = parent.getChildAdapterPosition(view);
        if(position > -1){//界面中的所有子view
            if(position == 0){//第一个位置,设置悬浮栏
                //在top留出一段距离
                outRect.set(0, mTitleHeight, 0, 0);//里面参数表示:左上右下的内边距padding距离
            }else{
                //当滑动到某一个item时(position位置)得到首字母,与上一个item对应的首字母不一致( position-1 位置),说明这是下一分组了
                if(mWords.get(position).getInitial() != null && !mWords.get(position).getInitial().equals(mWords.get(position-1).getInitial())){
                    //在top留出一段距离
                    outRect.set(0, mTitleHeight, 0, 0);
                }else{
                    //首字母相同说明是同一组的数据,比如都是 A 组下面的数据,那么就不需要再留出空间绘制悬浮栏了,共用同一个 A 组即可
                    outRect.set(0, 0, 0, 0);
                }
            }
        }
    }

    /**
     * 绘制文字和图形
     * */
    private void drawRectAndText(Canvas c, int left, int right, View child, RecyclerView.LayoutParams params, int position) {
        //1、画矩形悬浮栏
        //item可以有margin值不设置就默认为0,其中child.getTop()表示item距离父recycler view的距离,params.topMargin表示item的外边距,悬浮栏在item上方,那么悬浮栏的bottom就是child.getTop() - params.topMargin
        mPaint.setColor(Color.parseColor("#C5E4FD"));
        c.drawRect(left, child.getTop() - params.topMargin - mTitleHeight, right, child.getTop() - params.topMargin, mPaint);
        //2、画文字
        mPaint.setColor(Color.parseColor("#555555"));
        mPaint.getTextBounds(mWords.get(position).getInitial(), 0, mWords.get(position).getInitial().length(), mRectBounds);//将文字放到矩形中,得到Rect的宽高
        c.drawText(mWords.get(position).getInitial(), child.getPaddingLeft()+40, child.getTop() - params.topMargin - (mTitleHeight / 2 - mRectBounds.height() / 2), mPaint);
    }


}
子布局文件:word_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="60dp"
    android:background="#E3FAF9">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        >
        <TextView
            android:id="@+id/word_english"
            android:text="hello"
            android:textSize="20dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:id="@+id/word_chinese"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="20dp"
            android:text="释义:你好"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </RelativeLayout>
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#BCBCC0"/>

</LinearLayout>
主Activity:DictionaryActivity.java
public class DictionaryActivity extends Activity {
    private RecyclerView mRecyclerView;     //定义recycle view
    private WordAdapter mWordAdapter;       //定义适配器
    private WordItemDecoration mItemDecoration; //定义装饰
    private ArrayList<Word> mWords;     //定义数据

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dictionary);
        initData();
        mRecyclerView = findViewById(R.id.dictionary_recycler_view);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        mWordAdapter = new WordAdapter(this,mWords);
        mRecyclerView.setAdapter(mWordAdapter);

        mItemDecoration = new WordItemDecoration(this,mWords);
        mRecyclerView.addItemDecoration(mItemDecoration);
    }

    private void initData(){
        mWords = new ArrayList<>();
        mWords.add(new Word("absorb","吸收;吸引"));
        mWords.add(new Word("absurd","荒唐的"));
        mWords.add(new Word("acceptable","可接受的"));
        mWords.add(new Word("admit","承认"));
        mWords.add(new Word("advise","建议"));
        mWords.add(new Word("advocate","提倡,倡导"));
        mWords.add(new Word("back","背面,后部"));
        mWords.add(new Word("bad","坏的,有害的"));
        mWords.add(new Word("balloon","气球"));
        mWords.add(new Word("cafe","咖啡馆"));
        mWords.add(new Word("cake","蛋糕"));
        mWords.add(new Word("calculation","计算,计算结果"));
        mWords.add(new Word("calendar","日历,历书"));
        mWords.add(new Word("cherish","希望"));
        mWords.add(new Word("damage","损害,毁坏"));
        mWords.add(new Word("dancer","舞者; 舞女"));
        mWords.add(new Word("danger","危险"));
        mWords.add(new Word("each","各,各自"));
        mWords.add(new Word("earphone","耳机"));
        mWords.add(new Word("east","东,东方"));
        mWords.add(new Word("factory","工厂,制造厂"));
        mWords.add(new Word("fake","假货,膺品"));
        mWords.add(new Word("garbage",".垃圾,污物,废料"));
        mWords.add(new Word("gasolene","汽油"));
        mWords.add(new Word("gather","推测,推断"));

    }
}

主布局文件:activity_dictionary.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/dictionary_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

总结

1.再次感谢这篇博文的帮助
2.码字不易,若有帮助,给个关注和赞呗

在这里插入图片描述

发布了41 篇原创文章 · 获赞 102 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/baidu_41860619/article/details/105544539