此篇综合运用自定义ActionBar、ContextMenu、PopupWindow、Fragment、ViewPager 以及RecyclerView等实现微信页面效果。
同时这也是中国大学慕课移动终端应用开发的网课作业15,我会持续更新我的作业
这个说小不小的作品花了我两天的时间,时间花费的颇多。如果我的作品对您有所帮助的话,您的关注或是赞,都是对我的莫大支持。如果引用我的作品,请注明出处。
我尽可能符合了作业的题目要求,但是有些内容由于作业要求的组件或是方法达不到微信的界面效果,我进行相应的替换,在此说明。
内容较多,我准备分成三篇博客进行叙述分别为:
安卓作业----慕课移动应用开发作业15之模仿实现微信界面效果(一)
安卓作业----慕课移动应用开发作业15之模仿实现微信界面效果(二)
安卓作业----慕课移动应用开发作业15之模仿实现微信界面效果(三)
此篇是第三篇,主要写了fragment具体的实现部分,包括RecyclerView子布局的实现和一些效果。
废话说了那么多,先上效果图,如果各位看官还满意,那就继续读下去吧。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="65dp">
<RelativeLayout
android:layout_marginLeft="5dp"
android:layout_width="65dp"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_chat"
android:src="@drawable/girl"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"/>
RelativeLayout>
<RelativeLayout
android:layout_marginLeft="5dp"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1">
<TextView
android:id="@+id/text_chat_title"
android:layout_width="wrap_content"
android:layout_marginTop="12dp"
android:text="相亲相爱一家人"
android:textSize="18dp"
android:textColor="#333"
android:layout_height="25dp"/>
<TextView
android:id="@+id/text_chat_content"
android:layout_below="@id/text_chat_title"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:text="老妈:[好心情]"
android:textSize="10dp"
android:textColor="#ccc"/>
<TextView
android:id="@+id/text_chat_date"
android:layout_alignParentRight="true"
android:layout_marginTop="15dp"
android:layout_marginRight="20dp"
android:text="周日"
android:textColor="#ccc"
android:layout_width="30dp"
android:layout_height="20dp"
android:textSize="12dp"/>
<View
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#eee"/>
RelativeLayout>
LinearLayout>
public class ChatMsg {
private int imgId;//头像
private String title;//标题
private String content;//内容
private String date;//日期
/**
* 完全体构造方法
* */
public ChatMsg(int imgId, String title, String content, String date) {
this.imgId = imgId;
this.title = title;
this.content = content;
this.date = date;
}
/**
* 偷懒版
* */
public ChatMsg(String title, String content) {
this.title = title;
this.content = content;
this.imgId = R.drawable.girl;
this.date = "昨天";
}
/**
* 无参构造方法
* */
public ChatMsg() {
}
public int getImgId() {
return imgId;
}
public void setImgId(int imgId) {
this.imgId = imgId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
}
/**
* 微信聊天界面的适配器
* */
public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ViewHolder>{
private ArrayList<ChatMsg> mChatMsgs;
private Context mContext;//上下文对象
private LayoutInflater mInflater;
private int mPosition = -1;//记录当前位置
public int getPosition() {
return mPosition;
}
public void setPosition(int position) {
mPosition = position;
}
public void deleteItem(int position){
mChatMsgs.remove(position);
notifyDataSetChanged();
}
public void toFirstItem(int position){
ChatMsg chatMsg = mChatMsgs.remove(position);
mChatMsgs.add(0,chatMsg);
notifyDataSetChanged();
}
public ChatAdapter(Context context, ArrayList<ChatMsg> chatMsgs) {
mContext = context;
mChatMsgs = chatMsgs;
mInflater = LayoutInflater.from(mContext);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.chat_item, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
ChatMsg chatMsg = mChatMsgs.get(position);
holder.mImageView.setImageResource(chatMsg.getImgId());
holder.mTextViewTitle.setText(chatMsg.getTitle());
holder.mTextViewContent.setText(chatMsg.getContent());
holder.mTextViewDate.setText(chatMsg.getDate());
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mPosition = holder.getAdapterPosition();
return false;
}
});
}
@Override
public int getItemCount() {
return mChatMsgs.size();
}
/**
* view holder,实现上下文菜单接口
* */
class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener{
public ImageView mImageView;
public TextView mTextViewTitle,mTextViewContent,mTextViewDate;
public ViewHolder(@NonNull View itemView) {
super(itemView);
mImageView = itemView.findViewById(R.id.image_chat);
mTextViewTitle = itemView.findViewById(R.id.text_chat_title);
mTextViewContent = itemView.findViewById(R.id.text_chat_content);
mTextViewDate = itemView.findViewById(R.id.text_chat_date);
itemView.setOnCreateContextMenuListener(this);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
new MenuInflater(mContext).inflate(R.menu.chat_item_menu, menu);
}
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="50dp">
<RelativeLayout
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_marginLeft="5dp">
<ImageView
android:id="@+id/image_contact"
android:src="@drawable/girl"
android:layout_centerInParent="true"
android:layout_width="40dp"
android:layout_height="40dp"/>
RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<TextView
android:id="@+id/text_contact_name"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="新的朋友"
android:textSize="15dp"
android:textColor="#333"/>
<View
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#eee"/>
RelativeLayout>
LinearLayout>
public class ContactMsg {
private String groupName; //由于需要分组来给他们设置修饰需要一个组名
private int img; //图片资源
private String name; //名字
/**
* 构造方法
* */
public ContactMsg(String groupName, int img, String name) {
this.groupName = groupName;
this.img = img;
this.name = name;
}
/**
* 偷懒的构造方法
* */
public ContactMsg(String groupName, String name) {
this.groupName = groupName;
this.name = name;
this.img = R.drawable.girl;
}
public ContactMsg() {
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public int getImg() {
return img;
}
public void setImg(int img) {
this.img = img;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 通讯录联系人的适配器
* */
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ViewHolder> {
private Context mContext;
private ArrayList<ContactMsg> mContactMsgs;
private LayoutInflater mInflater;
public ContactAdapter(Context context, ArrayList<ContactMsg> contactMsgs) {
mContext = context;
mContactMsgs = contactMsgs;
mInflater = LayoutInflater.from(mContext);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.contact_item, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ContactMsg contactMsg = mContactMsgs.get(position);
holder.mImageView.setImageResource(contactMsg.getImg());
holder.mTextView.setText(contactMsg.getName());
}
@Override
public int getItemCount() {
return mContactMsgs.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
private ImageView mImageView;
private TextView mTextView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
mImageView = itemView.findViewById(R.id.image_contact);
mTextView = itemView.findViewById(R.id.text_contact_name);
}
}
}
public class ContactItemDecoration extends RecyclerView.ItemDecoration{
private ArrayList<ContactMsg> mContactMsgs;//设置数据
private Paint mPaint;//设置画悬浮栏的画笔
private Rect mRectBounds;//设置一个矩形,用于画文字
private int mTitleHeight;//设置悬浮栏的高度
private int mTextSize;//设置文字大小
private Context mContext;//设置上下文对象
public ContactItemDecoration(Context context, ArrayList<ContactMsg> contactMsgs) {
mContactMsgs = contactMsgs;
mContext = context;
//设置悬浮栏高度以及文字大小,为了统一尺寸规格,转换为像素
mTitleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, mContext.getResources().getDisplayMetrics());
mTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, mContext.getResources().getDisplayMetrics());
mRectBounds = new Rect();//初始化矩形
//初始化画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);//抗锯齿
mPaint.setDither(true);//防抖动
mPaint.setTextSize(mTextSize);
}
@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 (mContactMsgs.get(position).getGroupName().equals("0")){
//啥也不干
}else {
if(position == 0){//肯定是要绘制一个悬浮栏 以及 悬浮栏内的文字
//画矩形悬浮条以及文字
drawRectAndText(c, left, right, child, params, position);
}else{
if(mContactMsgs.get(position).getGroupName() != null && !mContactMsgs.get(position).getGroupName().equals(mContactMsgs.get(position - 1).getGroupName())){
//和上一个Tag不一样,说明是另一个新的分组
//画矩形悬浮条以及文字
drawRectAndText(c, left, right, child, params, position);
}else{
//说明是一组的,什么也不画,共用同一个Tag
}
}
}
}
}
}
@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 (mContactMsgs.get(position).getGroupName().equals("0")){
//啥也不干
}else {
if(position == 0){//第一个位置,设置悬浮栏
//在top留出一段距离
outRect.set(0, mTitleHeight, 0, 0);//里面参数表示:左上右下的内边距padding距离
}else{
//当滑动到某一个item时(position位置)得到首字母,与上一个item对应的首字母不一致( position-1 位置),说明这是下一分组了
if(mContactMsgs.get(position).getGroupName() != null && !mContactMsgs.get(position).getGroupName().equals(mContactMsgs.get(position-1).getGroupName())){
//在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("#eeeeee"));
c.drawRect(left, child.getTop() - params.topMargin - mTitleHeight, right, child.getTop() - params.topMargin, mPaint);
//2、画文字
mPaint.setColor(Color.parseColor("#888888"));
mPaint.getTextBounds(mContactMsgs.get(position).getGroupName(), 0, mContactMsgs.get(position).getGroupName().length(), mRectBounds);//将文字放到矩形中,得到Rect的宽高
c.drawText(mContactMsgs.get(position).getGroupName(), 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="horizontal"
android:layout_width="match_parent"
android:layout_height="50dp">
<RelativeLayout
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_marginLeft="5dp">
<ImageView
android:id="@+id/image_find"
android:src="@drawable/find_img1"
android:layout_centerInParent="true"
android:layout_width="30dp"
android:layout_height="30dp"/>
RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<TextView
android:id="@+id/text_find_name"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="朋友圈"
android:textSize="14dp"
android:textColor="#333"/>
<View
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#eee"/>
<ImageView
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:src="@drawable/right"
android:layout_width="15dp"
android:layout_height="15dp"/>
RelativeLayout>
LinearLayout>
public class FindMsg {
private int groupId;
private int img;
private String name;
public FindMsg(int groupId, int img, String name) {
this.groupId = groupId;
this.img = img;
this.name = name;
}
public FindMsg() {
}
public int getGroupId() {
return groupId;
}
public void setGroupId(int groupId) {
this.groupId = groupId;
}
public int getImg() {
return img;
}
public void setImg(int img) {
this.img = img;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class FindAdapter extends RecyclerView.Adapter<FindAdapter.ViewHolder> {
private Context mContext;
private ArrayList<FindMsg> mFindMsgs;
private LayoutInflater mInflater;
public FindAdapter(Context context, ArrayList<FindMsg> findMsgs) {
mContext = context;
mFindMsgs = findMsgs;
mInflater = LayoutInflater.from(mContext);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.find_item, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
FindMsg msg = mFindMsgs.get(position);
holder.mImageView.setImageResource(msg.getImg());
holder.mTextViewName.setText(msg.getName());
}
@Override
public int getItemCount() {
return mFindMsgs.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
private ImageView mImageView;
private TextView mTextViewName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
mImageView = itemView.findViewById(R.id.image_find);
mTextViewName = itemView.findViewById(R.id.text_find_name);
}
}
}
public class FindItemDecoration extends RecyclerView.ItemDecoration{
private ArrayList<FindMsg> mFindMsgs;//设置数据
private Paint mPaint;//设置画悬浮栏的画笔
private int mTitleHeight;//设置悬浮栏的高度
private Context mContext;//设置上下文对象
public FindItemDecoration(Context context, ArrayList<FindMsg> findMsgs) {
mFindMsgs = findMsgs;
mContext = context;
//设置悬浮栏高度,为了统一尺寸规格,转换为像素
mTitleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, mContext.getResources().getDisplayMetrics());
//初始化画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);//抗锯齿
mPaint.setDither(true);//防抖动
}
@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 (mFindMsgs.get(position).getGroupId()==0){
//啥也不干
}else {
if(position == 0){//肯定是要绘制一个悬浮栏
//画矩形悬浮条以及文字
drawRectAndText(c, left, right, child, params, position);
}else{
if(mFindMsgs.get(position).getGroupId() != 0 && !(mFindMsgs.get(position).getGroupId()==mFindMsgs.get(position - 1).getGroupId())){
//和上一个Tag不一样,说明是另一个新的分组
//画矩形悬浮条以及文字
drawRectAndText(c, left, right, child, params, position);
}else{
//说明是一组的,什么也不画,共用同一个Tag
}
}
}
}
}
}
@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 (mFindMsgs.get(position).getGroupId()==0){
//啥也不干
}else {
if(position == 0){//第一个位置,设置悬浮栏
//在top留出一段距离
outRect.set(0, mTitleHeight, 0, 0);//里面参数表示:左上右下的内边距padding距离
}else{
//当滑动到某一个item时(position位置)得到首字母,与上一个item对应的首字母不一致( position-1 位置),说明这是下一分组了
if(mFindMsgs.get(position).getGroupId() != 0 && !(mFindMsgs.get(position).getGroupId()==mFindMsgs.get(position - 1).getGroupId())){
//在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("#eeeeee"));
c.drawRect(left, child.getTop() - params.topMargin - mTitleHeight, right, child.getTop() - params.topMargin, mPaint);
}
}
在res目录下新建menu目录,创建此文件
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/item1_toFirst"
android:title="置顶"/>
<item android:id="@+id/item2_delete"
android:title="删除"/>
menu>
colors.xml
<resources>
<color name="colorPrimary">#dddcolor>
<color name="colorPrimaryDark">#dddcolor>
<color name="colorAccent">#D81B60color>
resources>
styles.xml
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
- "colorPrimary"
>@color/colorPrimary
- "colorPrimaryDark">@color/colorPrimaryDark
- "colorAccent">@color/colorAccent
style>
<style name="menu_layout1">
- "android:layout_width"
>120dp
- "android:layout_height">26dp
- "android:layout_marginBottom">5dp
style>
<style name="menu_image">
- "android:layout_width"
>20dp
- "android:layout_height">20dp
- "android:layout_gravity">center
style>
<style name="menu_text">
- "android:layout_width"
>wrap_content
- "android:layout_height">wrap_content
- "android:layout_marginLeft">8dp
- "android:layout_gravity">center
- "android:textSize">15dp
- "android:textColor">#333
style>
resources>
如果有什么问题,请私信联系我或者在评论区留言
码字不易,若有帮助,给个关注和赞呗