此博客通过RecyclerView、TextView等进行界面布局,使用自定义RecyclerView.Adapter、RecyclerViewAdapter.ViewHolder以及自定义RecyclerView.ItemDecoration实现分组列表以及悬浮顶部效果
同时这也是中国大学慕课移动终端应用开发的网课作业13,我会持续更新我的作业,如果有需要关注一下吧
1.非常感谢此篇博文以及博文作者,详细介绍了RecyclerView.ItemDecoration的用法,让我少花了很多时间。
2.自定义RecyclerView.ItemDecoration,即下面代码部分的WordItemDecoration.java类,我补充了许多注释,希望大家看的更轻松
3.如果想了解有关ItemDecoration更多知识,请戳我第一点的链接
4.我在作业要求的基础上进行拓展,做了一个单词查阅目录,我觉得这样作品可能更实用一些。
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;
}
}
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);
}
}
}
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 outRect
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.
*
* 检索给定项的任何偏移量。outRectoutRect
的每个字段指定项目视图应插入的像素数,
* 类似于填充或边距。默认实现将outRect的边界设置为0并返回
*
*
* If this ItemDecoration does not affect the positioning of item views, it should set
* all four fields of outRect
(left, top, right, bottom) to zero
* before returning.
* 如果此ItemDecoration不影响项视图的位置,则在返回之前,
* 它应将outRect
的所有四个字段(左、上、右、下)设置为零。
*
*
* 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);
}
}
<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>
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","推测,推断"));
}
}
<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.码字不易,若有帮助,给个关注和赞呗