第一部分:9.png图像的制作
遥想当年Android2.3的时代,手机不Root那基本没得玩,我那个时候荒废了不少时间搞什么系统美化,反编译啊什么的。虽然到头来竹篮打水,但是也算对计算机有了一定的兴趣。当时我最害怕就是9.png图像,因为只要涉及到修改这个的操作,那我是回编译不成功的。现在的我好好的瞥了两眼9.png的制作方法,这有什么难的?
首先《第一行代码中》的方法过时了,这项功能已经被集成到AS当中了,我们把图片放在文件夹里面后,点击图片右键选择create 9-Patch file
随后点击确认,在新生成的文件上操作就行了。具体的注意事项以及重点参见.9.png中四条黑线的意义。【注意:制作完成后,应该将原始文件删除,否则AS会分不清楚而报错。】
第二部分:制作聊天界面
- 开始之前先把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于图书馆