(代码过长,可以到http://blog.houxinlin.com/project/android/dingding.tar.gz进行下载)

一、效果图


都在骂钉钉的同时,我却仿照他的群列表开始学习_java

二、实现过程

首先定义数据信息类,包括基本的姓名,头像,加入时间。家长和学生扩展自BaseInfo

都在骂钉钉的同时,我却仿照他的群列表开始学习_java_02

学生信息定义如下,并扩展了一个家长集合。

都在骂钉钉的同时,我却仿照他的群列表开始学习_java_03

家长信息定义如下,其中扩展了和学生的关系名称,并持有一份学生的信息。

都在骂钉钉的同时,我却仿照他的群列表开始学习_java_04

MainActivity.java

这里的学生和家长信息使用Android提供的ArrayMap保存,key是单个字母,value是一堆StudentEntity集合,而StudentEntity中又包含一堆StudentFamily集合,存放多个家长信息。

首先是把所有学生的姓名拼音首字母提取出来,然后存放到ArrayMap中(ArrayMap可以通过下标进行访问元素)。最终形成单个字母对应多个学生,单个学生又对应多个家长信息的结构。

提取拼音可以使用com.belerweb:pinyin4这个框架。


  implementation 'com.belerweb:pinyin4j:2.5.0'
import android.os.Bundle;import android.util.ArrayMap;
import androidx.appcompat.app.AppCompatActivity;import androidx.recyclerview.widget.LinearLayoutManager;import androidx.recyclerview.widget.RecyclerView;
import com.hxl.gongzhonghaodemo.dingding.adapter.StudentAdapter;import com.hxl.gongzhonghaodemo.dingding.entitys.StudentEntity;import com.hxl.gongzhonghaodemo.dingding.entitys.StudentFamily;
import net.sourceforge.pinyin4j.PinyinHelper;
import java.time.LocalDateTime;import java.util.ArrayList;import java.util.List;
public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName();
private RecyclerView mRecyclerView;

private StudentAdapter mStudentAdapter;
private ArrayMap<Character, List<StudentEntity>> mStudentMap;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initView();        initData();
       mStudentAdapter = new StudentAdapter(this, mStudentMap);
       mRecyclerView.setAdapter(mStudentAdapter);

}
private void initData() {        mStudentMap = new ArrayMap<>();
List<StudentEntity> mStudentEntities    = new ArrayList<>();
       mStudentEntities.add(new StudentEntity.Builder() .setName("鲁迅") .setUrl("http://img5.imgtn.bdimg.com/it/u=3841760432,773912449&fm=11&gp=0.jpg") .addFamily(new StudentFamily("鲁豫", "", LocalDateTime.now(), "陌生人")) .addFamily(new StudentFamily("鲁智深", "", LocalDateTime.now(), "陌生人")) .build());        mStudentEntities.add(new StudentEntity.Builder() .setName("孔子") .setUrl("http://img2.imgtn.bdimg.com/it/u=3927250071,3411280749&fm=26&gp=0.jpg") .addFamily(new StudentFamily("孔融", "", LocalDateTime.now(), "儿子")) .addFamily(new StudentFamily("老子", "", LocalDateTime.now(), "陌生人")) .addFamily(new StudentFamily("孔明灯", "", LocalDateTime.now(), "天灯")) .build());
       mStudentEntities.add(new StudentEntity.Builder() .setName("李白") .setUrl("") .addFamily(new StudentFamily("李太白", "http://img0.imgtn.bdimg.com/it/u=3293099503,606929711&fm=26&gp=0.jpg", LocalDateTime.now(), "爸爸")) .addFamily(new StudentFamily("李太黑", "", LocalDateTime.now(), "妈妈")) .build());
       mStudentEntities.add(new StudentEntity.Builder() .setName("杜甫") .setUrl("http://img0.imgtn.bdimg.com/it/u=3593446461,3335288407&fm=26&gp=0.jpg") .addFamily(new StudentFamily("杜太甫", "http://img0.imgtn.bdimg.com/it/u=3293099503,606929711&fm=26&gp=0.jpg", LocalDateTime.now(), "爷爷")) .addFamily(new StudentFamily("杜绢花", "https://img.pconline.com.cn/images/upload/upc/tx/photoblog/1405/25/c1/34592098_34592098_1400979781687_mthumb.jpg", LocalDateTime.now(), "妈妈")) .build());
       mStudentEntities.add(new StudentEntity.Builder() .setName("白居易") .setUrl("http://img2.imgtn.bdimg.com/it/u=1473741299,1011020019&fm=26&gp=0.jpg") .addFamily(new StudentFamily("白行简", "http://img0.imgtn.bdimg.com/it/u=3293099503,606929711&fm=26&gp=0.jpg", LocalDateTime.now(), "姑姑")) .addFamily(new StudentFamily("白天鹅", "", LocalDateTime.now(), "宠物")) .addFamily(new StudentFamily("杜甫", "", LocalDateTime.now(), "兄弟"))
.build());
for (int i = 0; i < mStudentEntities.size(); i++) { StudentEntity item = mStudentEntities.get(i); char sort = getPinYinFirstLetter(item.getName().charAt(0)); if (mStudentMap.get(sort) == null) { List<StudentEntity> data = new ArrayList<>();                data.add(item);                mStudentMap.put(sort, data); } else { List<StudentEntity> studentEntities = mStudentMap.get(sort);                studentEntities.add(item); } }
}
private void initView() {        mRecyclerView = findViewById(R.id.recycleview);
       mRecyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false)); }
public char getPinYinFirstLetter(char str) { String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(str); if (pinyinArray==null){ return '#'; } return pinyinArray==null?'#':Character.toUpperCase(pinyinArray[0].charAt(0)); }
}

StudentAdapter.java 

RecyclerView的适配器,这里自定义了几个ViewGroup,如StudentViewGroup用来存放学生信息的视图列表。

import android.content.Context;import android.util.ArrayMap;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.TextView;
import androidx.annotation.NonNull;import androidx.recyclerview.widget.RecyclerView;
import com.hxl.gongzhonghaodemo.R;import com.hxl.gongzhonghaodemo.dingding.entitys.StudentEntity;import com.hxl.gongzhonghaodemo.dingding.ui.StudentViewGroup;
import java.util.List;
public class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.RecycleViewHolder> { private Context mContext;
private ArrayMap<Character,List<StudentEntity>> map;
public StudentAdapter(Context context, ArrayMap<Character, List<StudentEntity>> map) { this.mContext = context; this.map = map; }
@NonNull @Override public RecycleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View inflate = LayoutInflater.from(mContext).inflate(R.layout.item_group, parent, false); return new RecycleViewHolder(inflate); }
@Override public void onBindViewHolder(@NonNull RecycleViewHolder holder, int position) {
       holder.mTvSort.setText(map.keyAt(position)+""); List<StudentEntity> studentEntities = map.get(map.keyAt(position));
for (int i = 0; i <studentEntities.size() ; i++) {            holder.mStudentList.addStudent(studentEntities.get(i)); }
}
@Override public int getItemCount() { return map==null?0:map.size(); }
public class RecycleViewHolder extends RecyclerView.ViewHolder{ TextView mTvSort; StudentViewGroup mStudentList;
public RecycleViewHolder(@NonNull View itemView) { super(itemView);            mTvSort=itemView.findViewById(R.id.tv_sort);            mStudentList =itemView.findViewById(R.id.student_list); } }}

以下是学生信息的视图和家长信息的视图。


import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.LinearLayout;import android.widget.TextView;
import androidx.annotation.Nullable;
import com.hxl.gongzhonghaodemo.R;import com.hxl.gongzhonghaodemo.dingding.entitys.BaseInfo;import com.hxl.gongzhonghaodemo.dingding.entitys.StudentEntity;import com.hxl.gongzhonghaodemo.dingding.entitys.StudentFamily;import com.hxl.gongzhonghaodemo.dingding.utils.DisplayUtils;
public class BaseItemLayout extends LinearLayout { public BaseItemLayout(Context context) { super(context);        init(); }
public BaseItemLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs);        init(); }
public BaseItemLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);        init(); }
private void init() {         setOrientation(LinearLayout.VERTICAL); }
private View addItem(BaseInfo baseInfo, int resId) { if (baseInfo == null) { return null; } View mView = LayoutInflater.from(getContext()).inflate(resId, this, false); if (baseInfo instanceof StudentFamily){ ((TextView) mView.findViewById(R.id.tv_name)) .setText(((StudentFamily) baseInfo).getStudentEntity().getName()+"的"+((StudentFamily) baseInfo).getRelation()+"("+baseInfo.getName()+")"); }else { ((TextView) mView.findViewById(R.id.tv_name)).setText(baseInfo.getName()); } ProfilePicture mProfilePicture = mView.findViewById(R.id.im_profile_picture); int size = DisplayUtils.dip2px(getContext(), 40); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(size, size);        mProfilePicture.setLayoutParams(params);        mProfilePicture.setName(baseInfo); this.addView(mView); return mView; }
public View addItem(BaseInfo baseInfo) { if (this instanceof StudentViewGroup) { return addItem(baseInfo, R.layout.item_student); } else { return  addItem(baseInfo, R.layout.item_student_family); }
}}


public class StudentViewGroup extends BaseItemLayout { private static String TAG="StudentViewGroup"; private static Paint mNamePaint; public StudentViewGroup(Context context) { super(context); }
public StudentViewGroup(Context context, @Nullable AttributeSet attrs) { super(context, attrs); }
public StudentViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public void addStudent(StudentEntity studentEntity){ //添加自个 View mView = addItem(studentEntity);
//添加家人 FamilyViewGroup mFamilyList = mView.findViewById(R.id.family_list);        mFamilyList.addFamilyName(studentEntity.getStudentFamilies()); }
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); }}

import android.content.Context;import android.util.AttributeSet;import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.hxl.gongzhonghaodemo.dingding.entitys.StudentFamily;
import java.util.List;
public class FamilyViewGroup extends BaseItemLayout { public FamilyViewGroup(Context context) { super(context); }
public FamilyViewGroup(Context context, @Nullable AttributeSet attrs) { super(context, attrs); }
public FamilyViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }
public void addFamilyName(List<StudentFamily> studentFamilies) { for (int i = 0; i < studentFamilies.size(); i++) {            addItem(studentFamilies.get(i)); } }
}

其中自定义了个圆角视图RoundRelativeLayout和头像视图ProfilePicture。当没有头像url地址时,使用自己名字后两位,如果有,则使用Glide加载。

package com.hxl.gongzhonghaodemo.dingding.ui;
import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.RectF;import android.util.AttributeSet;import android.view.View;import android.widget.RelativeLayout;
public class RoundRelativeLayout extends RelativeLayout { private final RectF mRectF = new RectF(); private final Paint maskPaint = new Paint(); private final Paint zonePaint = new Paint(); private View mView; private Context mContext; public RoundRelativeLayout(Context context) { super(context);        init(); }
public RoundRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs);        init(); }
public RoundRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);        init(); } private void init() {
       maskPaint.setAntiAlias(true);        maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));        zonePaint.setColor(Color.WHITE); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom);
       mRectF.set(0,0,getWidth(),getHeight()); } @Override public void draw(Canvas canvas) {        canvas.saveLayer(mRectF, zonePaint, Canvas.ALL_SAVE_FLAG);        canvas.drawRoundRect(mRectF, getWidth(), getWidth(), zonePaint);        canvas.saveLayer(mRectF, maskPaint, Canvas.ALL_SAVE_FLAG); super.draw(canvas);        canvas.restore(); }

}

import android.content.Context;import android.graphics.Color;import android.util.AttributeSet;import android.view.Gravity;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.TextView;
import com.bumptech.glide.Glide;import com.hxl.gongzhonghaodemo.dingding.entitys.BaseInfo;
public class ProfilePicture extends RoundRelativeLayout {
public ProfilePicture(Context context, AttributeSet attrs) { super(context, attrs); }
public ProfilePicture(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); }
public ProfilePicture(Context context) { super(context); }
public void setName(BaseInfo baseInfo) { if (baseInfo.getProfilePicture() == null || baseInfo.getProfilePicture().length() == 0) { TextView mName = new TextView(getContext()); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);            mName.setLayoutParams(params); String name = baseInfo.getName().length() > 2 ?                    baseInfo.getName().substring(baseInfo.getName().length() - 2) : baseInfo.getName();            mName.setText(name);            mName.setGravity(Gravity.CENTER); this.addView(mName);            mName.setTextColor(Color.WHITE); return; } ImageView imageView =new ImageView(getContext());        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); Glide.with(imageView).load(baseInfo.getProfilePicture()).into(imageView); this.addView(imageView); }}