最近在做一个电影智能问答系统,需要有一个相应的界面来操作问答,而最近又在学android,使用决定采用recyclerview控件来写相关的内容。
android本身有一种相关的控件叫listview,但是相比recyclerview来说,Listview只能实现上下滑动而且扩展性不好。重要的是recyclerview可以实现复用,即已经移出屏幕的样式会在屏幕的下方进行复用。下面开始进行相关的操作。
一 前期准备
1 对话框素材
开始实现前需要有一个对话框,从icon网站上找到一个对话框png格式,背景透明作为所用的对话框。
将其导入drawable之后转换为 9 patch格式,可以确保放大的时候图像部分放大,从而确保不失真不变形。(改变图片左边和上边的黑色条来确定哪里被拉伸)
2 建立recyclerview依赖
因为recyclerview是新增空间,需要在项目的build.gradle中增加其依赖,注意此处的build.gradle的地址为app\build.gradle.在该文件下增加recyclerview依赖如下:
1 dependencies { 2 implementation fileTree(dir: 'libs', include: ['*.jar']) 3 implementation 'com.android.support:appcompat-v7:26.1.0' 4 implementation 'com.android.support.constraint:constraint-layout:1.1.2' 5 testImplementation 'junit:junit:4.12' 6 androidTestImplementation 'com.android.support.test:runner:1.0.2' 7 androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 8 9 //增加recyclerview依赖10 compile 'com.android.support:recyclerview-v7:26.1.0'11 }
新增后记得点击sync now来进行同步。
二 布局文件
1 主界面
首先写主界面,主界面其实有两大部分组成,第一部分是上面的recyclerview控件,存放发出和接收的消息。第二部分是位于底部的输入部分,其中包括editview控件(输入框)和button控件(发送按键)两个小部分。所以整体用线性布局将屏幕分为上下两部分,上部分高度设为0,权重为1,下部分高度设为适应高度。下部分再使用一个线性布局(默认为横向线性),将输入框设宽为0,权重为1;将按钮设为适应宽度,这样就可以很好的分屏了。
1 27 8 //上半部分的滑动列表 9 35 3614 15 //下部分的输入16 19 20 27 28 33 34
效果如下图:
这里布局的时候要善用weight(权重)这个属性。
比如说有一个linearlayout他的高度是100
a控件weight=1 b控件weight=2,c控件weight=3,d控件height=40,那么
a控件的高度就是 ((100-40)/(1+2+3)) *1
b控件的高度就是 ((100-40)/(1+2+3)) *2
c控件的高度就是 ((100-40)/(1+2+3)) *3
如果a不设置height=0dp,那么当a控件高度大于((100-40)/(1+2+3)) *1时,weight属性不起作用,设置等于0,那么weight属性什么时候都起作用。
2 对话框布局
对话框布局用在recyclerview布局里,即使recyclerview子项的布局。
将左右对话框的布局集合在一个布局里,再通过setVisiblity中view.visible和gone来设置布局可见不可见。
整体布局任为线性布局,分为两部分,分别是左对话框布局和右对话框布局,设置一个边框内填充的距离padding。左右对话框分别也是线性布局,里面包含一个textview文本框。左对话框设置背景图为准备好的左对话框素材,属性layout_gravity为lift 使其靠左布局,大小设置为适应性大小。里面的文本框也是适应性大小,布局属性为居中,设置一个边外框边界margin使字不会填充太满,字体颜色为黑色。右对话框和左对话框对称。代码如下:
16 7 41 4213 14 23 2421 22 30 31 39 40
示例图如下:
注意这里有一个坑,因为是直接从icon下载的素材,而且对话框的宽高为适应性大小,这个适应不但是适应里面的文字框大小,而且还适应背景图本身的大小,这就导致了如果输入文字很少,文本框就会远大于文字大小。解决方法:在ps里将素材进行重新的处理,将其缩小到60*60像素点左右大小,再通过9 patch转化图片,这样效果就很好了。
三 定义消息实体类 Message类
新建Message类,包括两个字段,content表示消息的内容,type表示消息的类型(TYPE_RECEIVED接收的消息 和TYPE_SEND发送的消息 )
1 public class Message { 2 // 设置收到消息类型为0 发送信息类型为1 3 public static final int TYPE_RECEIVED = 0; 4 public static final int TYPE_SEND = 1; 5 6 private String content; 7 private int type; 8 9 //构造函数赋值10 public Message(String content, int type) {11 this.content = content;12 this.type = type;13 }14 15 public String getContent() {16 return content;17 }18 19 public int getType() {20 return type;21 }22 }
四 Recyclerview适配器类
新建MsgAdapter类,代码如下:
1 public class MsgAdapter extends RecyclerView.Adapter{ 2 3 //展示数据源存放 4 private List mMsgList; 5 6 //内部类 将子项的所有布局进行传入匹配 7 static class ViewHolder extends RecyclerView.ViewHolder { 8 // 布局元素 9 LinearLayout leftLayout;10 LinearLayout rightLayout;11 TextView leftMsg;12 TextView rightMsg;13 14 // 内部类的构造函数传入recycler子项的布局15 public ViewHolder(View view) {16 super(view);17 leftLayout = (LinearLayout) view.findViewById(R.id.left_layout);18 rightLayout = (LinearLayout) view.findViewById(R.id.right_layout);19 leftMsg = (TextView) view.findViewById(R.id.left_msg);20 rightMsg = (TextView) view.findViewById(R.id.right_msg);21 }22 }23 24 // 传入要展示的数据源25 public MsgAdapter(List msgList) {26 mMsgList = msgList;27 }28 29 // 用于创建内部类viewholder实例 传入msg_item布局,在构造函数加载出所有布局30 @Override31 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {32 View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item, parent, false);33 return new ViewHolder(view);34 }35 36 // 对recyclerview子项数据进行赋值,在子项滚动到屏幕内时执行。通过position参数得到当前项具体内容37 @Override38 public void onBindViewHolder(ViewHolder holder, int position) {39 Message msg = mMsgList.get(position);40 if (msg.getType() == Message.TYPE_RECEIVED) {41 //收到消息,用左边的格式 右边格式隐藏42 holder.leftLayout.setVisibility(View.VISIBLE);43 holder.rightLayout.setVisibility(View.GONE);44 holder.leftMsg.setText(msg.getContent());45 } else if (msg.getType() == Message.TYPE_SEND) {46 //发送消息,用右边格式 左边格式隐藏47 holder.rightLayout.setVisibility(View.VISIBLE);48 holder.leftLayout.setVisibility(View.GONE);49 holder.rightMsg.setText(msg.getContent());50 }51 }52 53 // 返回recyclerview有多少子项54 @Override55 public int getItemCount() {56 return mMsgList.size();57 }58 59 }
这里的重点是重写三个方法 onCreateViewHolder(),onBindViewHolder()和getItemCount()。首先建立一个内部类将子项的所有布局进行传入匹配。
1 onCreateViewHolder()
用于创建内部类viewholder实例,在这个方法中传入msg_item布局,创建一个viewHolder实例,并把加载出来的布局传入到构造函数中,将viewholder的实例返回。
2 onBindViewHolder()
用于对recyclerview子项的数据进行赋值,在子项滚动到屏幕内时执行。通过position参数得到当前项的实例,在将其内容设置到实例中即可。
3 getItemCount()
返回recyclerview有多少子项
五 主函数
主函数主要是分配布局文件,并创建布局管理器,为滑动框设置布局管理器,设置适配器。之后为发送按钮设置点击事件,再初始化一下消息列表。具体的内容在代码的注释中有详细介绍。
1 public class MainActivity extends AppCompatActivity { 2 3 private ListmsgList = new ArrayList<>(); 4 private EditText inputText; 5 private Button send; 6 private RecyclerView msgRecyclerView; 7 private MsgAdapter adapter; 8 9 @Override10 protected void onCreate(Bundle savedInstanceState) {11 super.onCreate(savedInstanceState);12 setContentView(R.layout.activity_main);13 14 initMsgs();15 16 //分配布局文件17 inputText = (EditText) findViewById(R.id.input_text);18 send = (Button) findViewById(R.id.send);19 msgRecyclerView = (RecyclerView) findViewById(R.id.msg_recycler_view);20 21 //布局管理器22 LinearLayoutManager layoutManager = new LinearLayoutManager(this);23 //为滑动框设置布局管理器,设置适配器24 msgRecyclerView.setLayoutManager(layoutManager);25 adapter = new MsgAdapter(msgList);26 msgRecyclerView.setAdapter(adapter);27 //为发送按钮设置监听事件28 send.setOnClickListener(new View.OnClickListener() {29 @Override30 public void onClick(View view) {31 String content = inputText.getText().toString();32 //如果输入不为空33 if (!"".equals(content)) {34 Message msg = new Message(content, Message.TYPE_SEND);35 msgList.add(msg);36 //通知列表有新数据插入 这样数据才能在recyclerview中显示37 adapter.notifyItemInserted(msgList.size() - 1);38 //定位将显示的数据定位到最后一行,保证可以看到最后一条消息39 msgRecyclerView.scrollToPosition(msgList.size() - 1);40 inputText.setText("");41 }42 }43 });44 }45 46 //初始化消息列表47 private void initMsgs() {48 Message msg1 = new Message("Hallo,我是电影专家", Message.TYPE_RECEIVED);49 msgList.add(msg1);50 }51 }
完成效果图: