一、实验题目
Intent、Bundle的使用以及RecyclerView、ListView的应用
二、实现内容
本次实验模拟实现一个健康食品列表,有两个界面,第一个界面用于呈现食品列表 如下所示
数据在"manual/素材"目录下给出。
点击右下方的悬浮按钮可以切换到收藏夹
上面两个列表点击任意一项后,可以看到详细的信息:
三、课堂实验结果
(1)实验截图
1、主界面
2、点击一个食品进入详情界面
3、点击收藏按钮
4、进入收藏夹栏
5、长按收藏栏夹中食品
6、点击确定
7、点击星星
8、长按食品栏删除
(2)实验步骤以及关键代码
1、设置主界面,由一个RecyclerView和一个ListView以及一个FloatingActionButton组成。一开始启动界面为RecyclerView,暂时设置ListView属性为gone。
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ListView
android:visibility="gone"
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
2、设置RecyclerView。首先获取设置RecyclerView,使用setLayoutManager设置布局属性。然后要先自定义RecyclerView.ViewHolder。ViewHolder通常出现在适配器里,为的是listview滚动的时候快速设置值,而不必每次都重新创建很多对象,从而提升性能。使用一个SparseArray数组存储listItem中的子View。
private SparseArray<View> views;
private View view;
public MyViewHolder(Context _context, View _view, ViewGroup _viewGroup){
super(_view);
view = _view;
views = new SparseArray<View>();
}
注意ViewHolder尚未将子View缓存到SparseArray数组中时,仍然需要通过findViewById()创建View对象,如果已缓存,直接返回即可。
public <T extends View> T getView(int _viewId) {
View _view = views.get(_viewId);
if (_view == null) {
// 创建view
_view = view.findViewById(_viewId);
// 将view存入views
views.put(_viewId, _view);
}
return (T)_view;
}
之后需要自定义一个adapter。Adapter扮演着两个角色,一是根据不同ViewType创建与之相应的Item-Layout,二是访问数据集合并将数据绑定到正确的View上。因此需要重写一下两个函数。
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)创建Item视图,并返回相应的ViewHolder。
public void onBindViewHolder(final MyViewHolder holder, int position)绑定数据到正确的Item视图上。
convert函数是一个抽象方法,在声明adapter时需要实现它。作用是将数据绑定到RecyclerView的各个控件中。
final MyRecyclerViewAdapter myAdapter = new MyRecyclerViewAdapter<Map<String,String>>(MainActivity.this, R.layout.items, data) {
@Override
public void convert(MyViewHolder holder, Map<String,String> s) {
TextView name = holder.getView(R.id.name);
name.setText(s.get("name"));
TextView first = holder.getView(R.id.ID);
first.setText(s.get("ID"));
}
};
RecyclerView没有OnItemClickListener方法,需要在Adapter中实现。方法为:在Adapter中设置一个监听器,当itemView被点击时,调用该监听器并且将itemView的position作为参数传递出去。先添加接口
public interface OnItemClickListener{
void onClick(int position);
void onLongClick(int position);
}
public void setOnItemClickListener(OnItemClickListener _onItemClickListener) {
this.onItemClickListener = _onItemClickListener;
}
然后在onBindViewHolder()中添加重写函数。
3、设置ListView。ListView的原理和RecyclerView类似。这里使用了simpleadapter
构造一个SimpleAdapter,需要以下的参数:
1.Context context:上下文,这个是每个组件都需要的,它指明了SimpleAdapter关联的View的运行环境,也就是我们当前的Activity。
2.List<? extends Map
先创建一个simpleAdapter
simpleAdapter = new SimpleAdapter(this,data,R.layout.shoplist,
new String[] {"icon","itemname"},new int[]{R.id.Icon,R.id.Name});
然后用setAdapter函数设置adapter
之后要给listview设定点击函数,分别为单击和长按事件。
4、现在要在点击函数中加上页面跳转以及数据传递。在recycleview中绑定的是一个List<Map<String,String>>链表,分别保存了标签和名称。我这里传递的数据是该列表项所处的位置以及名称。所有的信息保存为一个string数组,然后检测该列表项的名称匹配数组中的位置,将该位置和名称新建一个bundle,再通过intent传入。
Bundle bundle = new Bundle();
bundle.putInt("position",index);
bundle.putString("name",_name);
intent.putExtras(bundle);
startActivityForResult(intent,1);
5、在详情页面内,使用getIntent()函数来接收信息,用getExtra获取bundle,然后判断该位置属于哪个食品,再绑定信息到各个控件中。
Intent intent=getIntent();
Bundle bundle =intent.getExtras();
String na=bundle.getString("name");
6、之后要进行详情页面的布局设计。我这里选择的是LinearLayout,把整个布局分为三块RelativeLayout。要设置顶部占三分之一,需要将整个LinearLayout的weightSum设为3,把顶部的RelativeLayout的layout_weight设为1.使用layout_alignParentLeft设置左对齐,底部对齐同理。要使得垂直居中需要设置layout_centerVertical属性为true。界面底部的listview的使用方法和之前类似,就不再详谈了。
7、现在要建立收藏的事件。和之前类似,依然使用一个bundle储存该食品的名称和位置。
Intent intent1 = new Intent(Info.this,MainActivity.class);
Bundle bundle1 = new Bundle();
bundle1.putInt("position",tag);
bundle1.putString("name",name[tag]);
intent1.putExtras(bundle);
值得一提的是,在之前的主界面中需要添加onActivityResult函数,来处理返回的值,也就是将收藏的值添加进之前建的List < Map < String,String>> 收藏列表中去。
protected void onActivityResult(int requestCode, int resultCode, Intent data1) {
super.onActivityResult(requestCode, resultCode, data1);
// RESULT_OK,判断另外一个activity已经结束数据输入功能,Standard activity result:
// operation succeeded. 默认值是-1
if (resultCode == 1) {
if (requestCode == 1) {
Bundle bundle=data1.getExtras();
int tag = bundle.getInt("position");
Map<String,String>temp = new LinkedHashMap<>();
temp.put("icon",ID[tag]);
temp.put("itemname",name[tag]);
data.add(temp);
simpleAdapter.notifyDataSetChanged();
}
}
}
8、星标的转换需要给该imagebutton加一个tag,再根据tag来setImageResource。
Star.setOnClickListener((view) ->{
int index=(Integer) view.getTag();
if (index==0)
{
Star.setTag(1);
Star.setImageResource(R.mipmap.full_star);
}
else
{
Star.setTag(0);
Star.setImageResource(R.mipmap.empty_star);
}
});
9、点击长按事件需要设置onItemLongClick函数。在recycleview中需要删除数组元素,并且将绑定的arraylist中也删除掉。在listview中需要获取所点击的view,然后获取名称。
LinearLayout layout = (LinearLayout)view;
TextView status = (TextView) layout.findViewById(R.id.Name);
String _name=status.getText().toString();
再通过判断位置来删除该收藏。
10、接下来还有悬浮窗的设计。添加依赖implementation
'com.android.support:design:27.1.1’设置布局文件
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn"
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:src="@mipmap/colletions"
android:backgroundTint="@color/colorWhite"
android:backgroundTintMode=“src_atop”
app:layout_constraintBottom_toBottomOf=“parent”
app:layout_constraintRight_toRightOf=“parent”
android:layout_margin=“25dp” />
之后设置点击函数。判断两个view的可见状态,然后分别改变可视状态,并且改变悬浮窗图片。
11、添加RecyclerView动画。这里使用的默认动画。使用DefaultItemAnimator函数
DefaultItemAnimator defaultItemAnimator = new DefaultItemAnimator();
defaultItemAnimator.setAddDuration(1000);
defaultItemAnimator.setRemoveDuration(1000);
recyclerView.setItemAnimator(defaultItemAnimator);
这里要注意的是,如果要采用默认动画,数据更新时不能使用Adapter.notifyDataSetChanged();而是要用Adapter.notifyItemChanged(int position)。
(3)实验遇到的困难以及解决思路
1、长按recycleview中控件删除时出现错误,提示数组超出范围。
解决思路:经过调试发现此时长按却进入了onclick函数,此时的positon为-1,数组name[position]自然不存在。经查阅后得知点击事件和长按点击事件是可以同时发生的。而此时可能处于一个两者之间的时间值。解决方法就是讲onLongClick中的返回参数改为true,使长点击事件成为一个独占事件。
2、recycleview一直无法导入依赖
解决思路:无法找到所对应版本的recycleview,因此直接从dependency中搜索相近版本的recycleview,加入进去。
3、布局不知道怎么使顶部占整体的三分之一
解决思路:令整个界面的weightSum为3,然后把顶部界面的RelativeLayout的layout_weight设为1.
4、传递页面时发现如果删除了一个列表项里的食品,则进入详情页面后与点击时的食品不匹配。
解决思路:删除食品时只是删除了adapter绑定的数据,并没有删除外部的字符数组的元素。应该将字符串的数组删除干净。
5、设置垂直居中时控件无法处在正确的位置
解决思路:因为垂直居中会使得控件处于整个父控件的中间,因为一开始我设置的整个中部空间为一个RelativeLayout,所以使得收藏夹按钮位于整个中间位置。经过修改后把收藏夹一行单独设为一个RelativeLayout,这样就可以垂直居中了。
四、实验思考及感想
1、这次实验主要涉及了页面之间的跳转和listview、recycleview的绑定。其中页面之间的跳转是安卓应用开发的重中之重,毕竟一个app不可能永远只有一个页面。页面跳转时需要注意传递的信息是否符合需要。还要注意在新的页面如何接收信息。recycleview等控件的绑定可以帮助我们更好地处理成批的信息,可以更方便之后的修改。
2、recycleview中自定义ViewHoler和adapter可以帮助我们实现许多新奇的效果。它具有低耦合高类聚的特性,过设置不同的LayoutManager,以及结合ItemDecoration , ItemAnimator,ItemTouchHelper,可以实现非常炫酷的效果。同时RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。直接省去了listview中convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
3、我们再注入依赖时必须注意自己的版本号,如果版本号不匹配,将无法build,甚至于去掉该依赖后仍然会有报错。有时候需要重启一下as才行。如果实在不能匹配,可以考虑直接从project structure中的dependencies里添加依赖。
4、布局时要熟悉各种布局的形式与效果,尽量尝试每一种布局,这样到真正使用时可以根据需求更好地选择正确的布局方式。