此篇綜合運用自定義ActionBar、ContextMenu、PopupWindow、Fragment、ViewPager 以及RecyclerView等實現微信頁面效果。
同時這也是中國大學慕課移動終端應用開發的網課作業15,我會持續更新我的作業
說明
說明1
這個說小不小的作品花了我兩天的時間,時間花費的頗多。如果我的作品對您有所幫助的話,您的關注或是贊,都是對我的莫大支持。如果引用我的作品,請註明出處。
我儘可能符合了作業的題目要求,但是有些內容由於作業要求的組件或是方法達不到微信的界面效果,我進行相應的替換,在此說明。
說明2
內容較多,我準備分成三篇博客進行敘述分別爲:
安卓作業----慕課移動應用開發作業15之模仿實現微信界面效果(一)
安卓作業----慕課移動應用開發作業15之模仿實現微信界面效果(二)
安卓作業----慕課移動應用開發作業15之模仿實現微信界面效果(三)
說明3
此篇是第三篇,主要寫了fragment具體的實現部分,包括RecyclerView子佈局的實現和一些效果。
效果圖
廢話說了那麼多,先上效果圖,如果各位看官還滿意,那就繼續讀下去吧。
代碼
1.微信RecyclerView的實現
佈局子文件 chat_item.xml
<?xml version="1.0" encoding="utf-8"?>
<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>
對應的模型 ChatMsg.java
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;
}
}
對應的適配器 ChatAdapter.java
/**
* 微信聊天界面的適配器
* */
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);
}
}
}
2.通訊錄RecyclerView的實現
佈局子文件 contact_item.xml
<?xml version="1.0" encoding="utf-8"?>
<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>
對應的模型 ContactMsg.java
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;
}
}
對應的適配器 ContactAdapter.java
/**
* 通訊錄聯繫人的適配器
* */
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);
}
}
}
對應的裝飾ContactItemDecoration .java
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);
}
}
3.發現RecyclerView的實現
佈局子文件 find_item.xml
<?xml version="1.0" encoding="utf-8"?>
<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>
對應的模型 FindMsg.java
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;
}
}
對應的適配器 FindAdapter.java
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);
}
}
}
對應的裝飾FindItemDecoration .java
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);
}
}
4.對遺漏的文件進行補充
菜單佈局文件 chat_item_menu.xml
在res目錄下新建menu目錄,創建此文件
<?xml version="1.0" encoding="utf-8"?>
<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>
values文件夾下修改colors.xml和styles.xml文件
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#ddd</color>
<color name="colorPrimaryDark">#ddd</color>
<color name="colorAccent">#D81B60</color>
</resources>
styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="menu_layout1">
<item name="android:layout_width">120dp</item>
<item name="android:layout_height">26dp</item>
<item name="android:layout_marginBottom">5dp</item>
</style>
<style name="menu_image">
<item name="android:layout_width">20dp</item>
<item name="android:layout_height">20dp</item>
<item name="android:layout_gravity">center</item>
</style>
<style name="menu_text">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginLeft">8dp</item>
<item name="android:layout_gravity">center</item>
<item name="android:textSize">15dp</item>
<item name="android:textColor">#333</item>
</style>
</resources>
總結
如果有什麼問題,請私信聯繫我或者在評論區留言
碼字不易,若有幫助,給個關注和讚唄