安卓作業----慕課移動應用開發作業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.碼字不易,若有幫助,給個關注和讚唄

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章