此博客通過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.碼字不易,若有幫助,給個關注和讚唄