《第一行程式碼》 編寫精美的聊天介面
第一部分:9.png影象的製作
遙想當年Android2.3的時代,手機不Root那基本沒得玩,我那個時候荒廢了不少時間搞什麼系統美化,反編譯啊什麼的。雖然到頭來竹籃打水,但是也算對計算機有了一定的興趣。當時我最害怕就是9.png影象,因為只要涉及到修改這個的操作,那我是回編譯不成功的。現在的我好好的瞥了兩眼9.png的製作方法,這有什麼難的?
首先《第一行程式碼中》的方法過時了,這項功能已經被整合到AS當中了,我們把圖片放在資料夾裡面後,點選圖片右鍵選擇create 9-Patch file
隨後點選確認,在新生成的檔案上操作就行了。具體的注意事項以及重點參見.9.png中四條黑線的意義
第二部分:製作聊天介面
- 開始之前先把RecyclerView引過來。開啟app中的build.gradle,在其中的dependencies中加上
implementation 'com.android.support:recyclerview-v7:28.0.0'
這個與書上的不同,因為過時了。版本號與com.android.support:appcompat-v7:28.0.0相同即可。
隨後同步build.gradle
- 因為開啟就顯示介面,所以肯定是在主佈局裡面寫。我們需要一個線性佈局,佈局應該是垂直的。上面是RecyclerCiew,底部有兩個控制元件,左邊是EditView右邊是Button,但是因為主佈局是垂直的,所以我們在佈局中再加入一個水平的佈局,底下的兩個控制元件都在水平佈局裡面。
<?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="match_parent" android:background="#d8e0e8" > <android.support.v7.widget.RecyclerView android:id="@+id/msg_recycle_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <!--下面的佈局是水平方向的,不寫出來預設就是水平--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <EditText android:id="@+id/input_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="Type something here" android:maxLines="2" /> <Button android:id="@+id/send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send" /> </LinearLayout> </LinearLayout>
這裡複習一下layout_weight的用法,我印象中這個只有在LinearLayout中才可以使用,使用之後,系統會把所有元件的weight相加,然後把空間按需分配。但是與match_parent不同的是,系統會給其他控制元件保留最小的尊嚴。比如上面的的栗子,RecyclerView的weight為1,但它下面還有輸入框和按鈕,所以系統為下面兩個控制元件留了位置。又比如下面輸入框的weight也是1,系統在水平方向上為button保留了位置。【水平狀態下,weight抵消width;垂直狀態下,weight抵消height】
-
因為我們使用了RecyclerView,所以我們需要為其編寫子項佈局。新建一個佈局檔案,我們意識到這個佈局檔案裡面只會出現TextView,但是發出和接受訊息的方向是不同的,比如發出的都會在右面出現,接受的都會在左面出現。所以我們要宣告兩個TextView。接下來就有意思了,書上面給出的栗子是每個TextView的外層嵌套了一個LinearLayout,而如果你直接寫出來兩個TextView在一個線性佈局裡面也是可以通過的,這是兩者的區別。
可以看得出來,沒有巢狀的 字幾乎是貼著圖片的,而巢狀著的顯得更加的自然。這就是與郭神的差距啊。
<?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="wrap_content"
android:padding="10dp" >
<LinearLayout
android:id="@+id/left_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:background="@drawable/message_left" >
<TextView
android:id="@+id/left_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#fff" />
</LinearLayout>
<LinearLayout
android:id="@+id/right-layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:background="@drawable/message_right" >
<TextView
android:id="@+id/right_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp" />
</LinearLayout>
</LinearLayout>
這裡說一下layout_margin的含義,它線上性佈局裡的意思是view距離父view的距離。正因為如此,TextView才可以與巢狀著的LinearLayout形成縫隙,這是不巢狀沒法做出來的。
-
第三部分:RecyclerView介面卡和資訊類
資訊類Msg,我們的訊息分為接收的訊息和傳送的訊息,他們唯一的區別就是在螢幕上的位置不同,因此我們需要定義訊息的狀態,int就可以。有了資訊的狀態,當然還需要資訊的內容啊,接下來定義幾個接收器就可以了。
package com.example.uibestpractice;
public class Msg {
//普通的記錄訊息的類
public static final int TYPE_RECEIVED=0;
public static final int TYPE_SEND=1;
private String content;
private int Type;
public Msg(String content,int Type)
{
this.content=content;
this.Type=Type;
}
public String getContent()
{
return content;
}
public int getType()
{
return Type;
}
}
-
製作RecyclerView的介面卡
開始製作介面卡,都是一樣的套路,首先宣告一個類MsgAdapter繼承RecyclerView.Adapter<MsgAdapter.ViewHolder>,其中ViewHolder是我們定義在類中的一個靜態類,繼承自RecyclerView.ViewHolder,在這個靜態類中我們需要例項化我們RecyclerView佈局中的控制元件,有兩個LinearLayout和兩個TextView,都在建構函式中進行,但是建構函式必須滿足父類的建構函式,因此傳一個view進去就可以了。
我們的主類的建構函式很簡單,只需要接收一個List即可,將接受的List傳給欄位。這個List裡面儲存的都是Msg類的物件。
隨後重寫 onCreateViewHolder,onBindViewHolder以及getItemCount三種方法。先來說最簡單的getItemCount,只需要返回list的size即可。
接著看onCreateVIewHolder,這個方法是動態RecyclerView例項的,所以宣告
View view =LayoutInflater.from(paremt.context).inflate(子項id,parent,false);
retuen new ViewHolder(view);
再來onBindViewHolder,這個是要賦給內部類欄位屬性,首先先建立對應position的Msg的物件,如果這個物件的是接受的訊息,那麼我們就把對應傳送訊息的LinearLayout,view.GONE掉。,顯示出來接受訊息的LinearLayout。注意一定是GONE,不是INVISIBLE,否則後果如下:
上正確的程式碼
package com.example.uibestpractice;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.List;
public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder> {
private List<Msg> mMsgList;
static class ViewHolder extends RecyclerView.ViewHolder
{
LinearLayout leftLayout;
LinearLayout rightLayout;
TextView leftMsg;
TextView rightMsg;
public ViewHolder(View view)
{
super(view);
leftLayout=(LinearLayout) view.findViewById(R.id.left_layout);//因為layout是在檔案中,所以是R.id
rightLayout=(LinearLayout) view.findViewById(R.id.right_layout);
leftMsg=(TextView) view.findViewById(R.id.left_msg);
rightMsg=(TextView) view.findViewById(R.id.right_msg);
}
}
public MsgAdapter(List<Msg> mMsgList){
this.mMsgList=mMsgList;
}
@Override
public ViewHolder onCreateViewHolder( ViewGroup parent, int i) {
View view=LayoutInflater.from(parent.getContext()).inflate(i,parent,false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Msg msg=mMsgList.get(position);
if(msg.getType()== Msg.TYPE_RECEIVED)
{
holder.leftLayout.setVisibility(View.VISIBLE);
holder.rightLayout.setVisibility(View.GONE);
holder.leftMsg.setText(msg.getContent());
}
else if (msg.getType()==Msg.TYPE_SEND)
{
holder.rightLayout.setVisibility(View.VISIBLE);
holder.leftLayout.setVisibility(View.GONE);
holder.rightMsg.setText(msg.getContent());
}
}
@Override
public int getItemCount() {
return mMsgList.size();
}
}
- 最後就是在主類中例項化了
首先例項化RecyclerView;因為我們要點選button,所以也有它的份;例項化button為了獲得EditView的內容啊,所以這個編輯框也得例項化;例項化介面卡;最後規定RecyclerView的格式,這次依然是傳統格式。
我們還需要為button建立一個監聽器,我們需要將編輯框的內容傳送到RecyclerView上
①獲取內容,將字串新增到List當中去。
②呼叫介面卡的notifyItemInserted方法,通知有新的資料加入了,趕緊將這個資料加到RecyclerView上面去。
③呼叫RecyclerView的scrollToPosition方法,以保證一定可以看的到最後發出的一條訊息。
最後我沒有按照書上的栗子初始化List,修改了一下,現在你可以自己和自己聊天了。
package com.example.uibestpractice;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<Msg> msgList=new ArrayList<>();
private EditText editText;
private Button button;
private RecyclerView recyclerView;
private MsgAdapter adapter;
private boolean Send=true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText=(EditText) findViewById(R.id.input_text);
button=(Button) findViewById(R.id.send);
recyclerView=(RecyclerView) findViewById(R.id.msg_recycle_view);
adapter=new MsgAdapter(msgList);
LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(adapter);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content=editText.getText().toString();
if (!"".equals(content))
{
Msg msg;
if (Send)
{ msg=new Msg(content,Msg.TYPE_SEND);}
else
{ msg=new Msg(content,Msg.TYPE_RECEIVED);
}
Send=!Send;
msgList.add(msg);
adapter.notifyItemInserted(msgList.size()-1);
recyclerView.scrollToPosition(msgList.size()-1);
editText.setText("");
}
}
});
}
希望所有認真的同學都能有一個好的結果,祝你晚安。
------2018.10.3於圖書館