LZ-Says:最近天凉了,丝丝凉意变成了浓浓冻得哆嗦了。穿个衣服都感觉很是痛苦了。
点背的家里突然发现漏水,住了小一年了,才发现,怪不得物业大爷说你们几个真能用水,涨钱。。。
然而,今天才看见原来是漏水导致的,最悲剧的是修水管修了半天,饿的腿都哆嗦,我滴神。。。
前言
上面罗里吧嗦一大推,各位见谅哈~
最近有幸学习了泥阿布先生的关于RecyclerView优雅实现复杂布局,今天趁着还有点想法,赶紧写下来,想看视频,想看原创?别急,文某附上链接地址,我们都是有良好职业道德和职业素养的标准程序猿`
老规矩,放图
本文目标
阅读完本文,你会get如下技能:
- 使用RecyclerView实现复杂布局;
- 使用RecyclerView实现ListView以及GirdView混排效果;
- 通过优化Model加深了解通过RecyclerView实现混排效果。
希望通过不断get小技能,让你在Android路上一路畅通无堵~
废话不多说,开车~
一、使用RecyclerView实现复杂布局
首先我们先做一些基本操作,类似于控件放置,初始化(控件,模拟数据)等。
1.1 引用RecyclerView,填充布局
1)Android Studio的小伙伴记得引入有关RecyclerView依赖;
2)Eclipse的小伙伴记得拷贝本地SDK下载包含的RecyclerView jar包即可。
布局放置如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.materialdesignstudy.complexrecycler.itemone.ComplexOneActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/id_recy"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
放置好了一个RecyclerView,下面模拟数据时,来一个Model类。
1.2 设置Model类
package com.materialdesignstudy.complexrecycler.itemone;
/**
* Created by HLQ on 2017/10/22
*/
public class DataModel {
// 状态标识位
public static final int TYPE_ONE = 1;
public static final int TYPE_TWO = 2;
public static final int TYPE_THREE = 3;
public int type; // 类型 针对某种样式文件 也就是布局
public int avatarColor; // 头像颜色
public String name; // 姓名
public String content; // 内容
public int contentColor; // 内容颜色
}
1.3 定义item布局
如效果图一般,我们创建如下item布局文件:
item_type_one:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tool="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<ImageView
android:id="@+id/avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="15dp"
tool:background="@color/blue" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
tool:text="贺利权" />
</LinearLayout>
效果如下:
item_type_two:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tool="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<ImageView
android:id="@+id/avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="15dp"
tool:background="@color/blue" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
tool:text="贺利权" />
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
tool:text="内容" />
</LinearLayout>
</LinearLayout>
效果如下:
item_layout_three:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tool="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<ImageView
android:id="@+id/avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="15dp"
tool:background="@color/blue" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/avatar"
android:orientation="vertical">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
tool:text="贺利权" />
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
tool:text="内容" />
</LinearLayout>
<ImageView
android:id="@+id/contentImg"
android:layout_width="80dp"
android:layout_height="40dp"
android:layout_alignBottom="@+id/linearLayout"
android:layout_alignParentEnd="true"
android:layout_marginRight="15dp"
tool:background="@color/blue" />
</RelativeLayout>
效果如下:
1.4 编写对应ViewHolder
由于我们的ViewHolder都需要构造以及数据绑定,由此,我们拓展一个封装ViewHolder类,让其他的ViewHolder直接集成此类即可。
定义封装ViewHolder类:
package com.materialdesignstudy.complexrecycler.itemone;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* ViewHolder封装类
* Created by HLQ on 2017/10/22
*/
public abstract class TypeAbstractViewHolder extends RecyclerView.ViewHolder {
public TypeAbstractViewHolder(View itemView) {
super(itemView);
}
/**
* 数据绑定
*
* @param dataModel 数据源 由于我们模拟数据实体类暂定为DataModel 这里直接传入这个即可
* 后期可根据项目实际需求去设置 也可以直接写为T
*/
public abstract void bindHolder(DataModel dataModel);
}
定义Item1对应的ViewHolder:
package com.materialdesignstudy.complexrecycler.itemone;
import android.graphics.Color;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.materialdesignstudy.R;
/**
* Created by HLQ on 2017/10/22
*/
public class TypeOneViewHolder extends TypeAbstractViewHolder {
public ImageView avatar;
public TextView name;
public TypeOneViewHolder(View itemView) {
super(itemView);
// 初始化控件
avatar = itemView.findViewById(R.id.avatar);
name = itemView.findViewById(R.id.name);
// 为了区分item 这里为item设置背景颜色
itemView.setBackgroundColor(Color.YELLOW);
}
@Override
public void bindHolder(DataModel dataModel) {
// 数据绑定
avatar.setBackgroundResource(dataModel.avatarColor);
name.setText(dataModel.name);
}
}
item2对应ViewHolder:
package com.materialdesignstudy.complexrecycler.itemone;
import android.graphics.Color;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.materialdesignstudy.R;
/**
* Created by HLQ on 2017/10/22
*/
public class TypeTwoViewHolder extends TypeAbstractViewHolder {
public ImageView avatar;
public TextView name, content;
public TypeTwoViewHolder(View itemView) {
super(itemView);
avatar = itemView.findViewById(R.id.avatar);
name = itemView.findViewById(R.id.name);
content = itemView.findViewById(R.id.content);
itemView.setBackgroundColor(Color.GRAY);
}
@Override
public void bindHolder(DataModel dataModel) {
avatar.setBackgroundResource(dataModel.avatarColor);
name.setText(dataModel.name);
content.setText(dataModel.content);
}
}
item3对应ViewHolder:
package com.materialdesignstudy.complexrecycler.itemone;
import android.graphics.Color;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.materialdesignstudy.R;
/**
* Created by HLQ on 2017/10/22
*/
public class TypeThreeViewHolder extends TypeAbstractViewHolder {
public ImageView avatar, contentImg;
public TextView name, content;
public TypeThreeViewHolder(View itemView) {
super(itemView);
avatar = itemView.findViewById(R.id.avatar);
name = itemView.findViewById(R.id.name);
content = itemView.findViewById(R.id.content);
contentImg = itemView.findViewById(R.id.contentImg);
itemView.setBackgroundColor(Color.BLUE);
}
@Override
public void bindHolder(DataModel dataModel) {
avatar.setBackgroundResource(dataModel.avatarColor);
name.setText(dataModel.name);
content.setText(dataModel.content);
contentImg.setBackgroundResource(dataModel.contentColor);
}
}
1.5 定义空的Adapter类,初始化控件、模拟数据
package com.materialdesignstudy.complexrecycler.itemone;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import com.materialdesignstudy.R;
import java.util.ArrayList;
import java.util.List;
/**
* 复杂布局实现
* Created by HLQ on 2017/10/22
*/
public class ComplexOneActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private OneAdapter mOneAdapter;
private int mColor[] = {android.R.color.holo_red_light,
android.R.color.holo_green_light,
android.R.color.holo_blue_light};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_complec_one);
initItem();
initData();
}
private void initData() {
List<DataModel> dataList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
int type = (int) ((Math.random() * 3) + 1); // 随机type
DataModel dataModel = new DataModel();
dataModel.avatarColor = mColor[type - 1];
dataModel.type = type;
dataModel.name = "name" + i;
dataModel.content = "content:" + i;
dataModel.contentColor = mColor[(type + 1) % 3];
dataList.add(dataModel);
}
mOneAdapter.addList(dataList);
mOneAdapter.notifyDataSetChanged();
}
private void initItem() {
mRecyclerView = findViewById(R.id.id_recy);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
mOneAdapter = new OneAdapter(this);
mRecyclerView.setAdapter(mOneAdapter);
}
}
以上这些都是很easy的,这里就不过多说明了。关键内容如下,让我们一起瞅瞅Adapter又是如何编写的。
1.6 打造属于you的Adapter
首先,这里需要再次说明一点:
- getItemViewType():返回当前ItemView类型,而我们将会根据此类型进行相应的业务处理。
嗯那,就是这样,下面直接放出代码:
package com.materialdesignstudy.complexrecycler.itemone;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.materialdesignstudy.R;
import java.util.ArrayList;
import java.util.List;
/**
* Created by HLQ on 2017/10/22
*/
public class OneAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private LayoutInflater mLayoutInflater;
private List<DataModel> mDataList = new ArrayList<>();
public OneAdapter(Context context) {
this.mLayoutInflater = LayoutInflater.from(context);
}
public void addList(List<DataModel> dataModelList) {
this.mDataList.addAll(dataModelList);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 通过获取不同的viewType,返回对应的ViewHolder以及引入的布局文件进行渲染,从而实现复杂布局实现
switch (viewType) {
case DataModel.TYPE_ONE:
return new TypeOneViewHolder(mLayoutInflater.inflate(R.layout.item_type_one, parent, false));
case DataModel.TYPE_TWO:
return new TypeTwoViewHolder(mLayoutInflater.inflate(R.layout.item_type_two, parent, false));
case DataModel.TYPE_THREE:
return new TypeThreeViewHolder(mLayoutInflater.inflate(R.layout.item_type_three, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
// 由于子类ViewHolder继承自TypeAbstractViewHolder,这里可以直接通过强制转化去绑定数据
// 这也是我们为什么要对ViewHolder进行封装的一点优势
((TypeAbstractViewHolder) holder).bindHolder(mDataList.get(position));
}
@Override
public int getItemCount() {
return mDataList.size();
}
@Override
public int getItemViewType(int position) {
return mDataList.get(position).type;
}
}
到现在为止,我们已经完成了通过RecyclerView实现复杂布局,不信你可以运行下你现在的代码喽~
二、使用RecyclerView实现ListView以及GirdView混排效果
本小节内容主要关注于LayoutManager的setSpanSizeLookup()方法,下面对其进行简述。
setSpanSizeLookup(): 主要使用这个方法来展示不同的 item 屏幕跨度
- spanCount:在创建 GridLayoutManager 对象的时候构造方法需要传入这个参数,也就是设置每行排列 item 个数。
- spanSize:在 setSpanSizeLookup() 方法中,这个方法返回的是当前位置的 item 跨度大小。
简图如下:
嗯哼,简单了解后,我们撸码呗`
package com.materialdesignstudy.complexrecycler.itemtwo;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.materialdesignstudy.R;
import com.materialdesignstudy.complexrecycler.itemone.DataModel;
import com.materialdesignstudy.complexrecycler.itemone.OneAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* 使用RecyclerView实现ListView+GridView混排效果
* create by heliquan at 2017年10月23日
* 重点关注setSpanSizeLookup即可 获取当前跨度
*/
public class ComplexTwoActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private OneAdapter mOneAdapter;
private int mColor[] = {android.R.color.holo_red_light,
android.R.color.holo_green_light,
android.R.color.holo_blue_light};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_complec_one);
initItem();
initData();
}
private void initData() {
List<DataModel> dataList = new ArrayList<>();
for (int i = 0; i < 30; i++) {
// 模拟不同itemType
int type;
if (i < 5 || (i > 15 && i < 20)) {
type = 1;
} else if (i < 10 || i > 20) {
type = 2;
} else {
type = 3;
}
DataModel dataModel = new DataModel();
dataModel.avatarColor = mColor[type - 1];
dataModel.type = type;
dataModel.name = "name" + i;
dataModel.content = "content:" + i;
dataModel.contentColor = mColor[(type + 1) % 3];
dataList.add(dataModel);
}
mOneAdapter.addList(dataList);
mOneAdapter.notifyDataSetChanged();
}
private void initItem() {
mRecyclerView = findViewById(R.id.id_recy);
// 更换显示布局样式为GirdLayoutManager
final GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
// 获取当前位置下的ItemViewType
int type = mRecyclerView.getAdapter().getItemViewType(position);
// 由于item1和item2可以正常显示 而item3需要横跨2列
// 所以需要在这里对item3进行单独处理
if (type == DataModel.TYPE_THREE) {
return gridLayoutManager.getSpanCount(); // item3需要横跨2列
} else {
return 1;
}
}
});
mRecyclerView.setLayoutManager(gridLayoutManager);
mOneAdapter = new OneAdapter(this);
mRecyclerView.setAdapter(mOneAdapter);
// 为了显示效果更好 在此添加分割线
mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
GridLayoutManager.LayoutParams layoutParams = (GridLayoutManager.LayoutParams) view.getLayoutParams();
int spanSize = layoutParams.getSpanSize(); // 获取当前位置的 item 跨度大小
int spanIndex = layoutParams.getSpanIndex(); // 获取每行排列 item 个数
outRect.top = 20;
// 如果当前跨度不等于当前索引 表示当前不属于item3
if (spanSize != gridLayoutManager.getSpanCount()) { // 针对item1,item2做分割线处理
if (spanIndex == 1) {
outRect.left = 10;
} else {
outRect.right = 10;
}
}
}
});
}
}
其实这部分的关键点就是在于那个方法,大家有时间可以细致了解。
好了,到现在为止,第二小节也over了。Nice。
三、复杂Model应对之策
以上俩点都是基于相同的Model,那么在实际开发中,我们Model也许各不相同,那么这时候该如何处理呢?
这里为大家提供下思路,可以顺着延伸下。
- 改造ViewHolder封装类,之前我们直接采用固定Model,而今替换T即可,而对应子类ViewHolder继承时需要指定Model类型;
- Adapter中却需要记录当前itemType对应包含List.size()以及itemType对应类型。原因在于我们需要将数据进行组合,因为后台的数据不可能会按照我们所想的来,只能进行二次拼接。
关键内容如上,下面就简单贴出关键代码,完整代码请在下方查看GitHub。
3.1 改造后的ViewHolder封装类
package com.materialdesignstudy.complexrecycler.itemthree;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.materialdesignstudy.complexrecycler.itemone.DataModel;
/**
* Created by HLQ on 2017/10/22
*/
public abstract class TypeAbstractViewHolder<T> extends RecyclerView.ViewHolder {
public TypeAbstractViewHolder(View itemView) {
super(itemView);
}
public abstract void bindHolder(T dataModel);
}
3.2 改造后的数据初始化
private void initData() {
List<DataModelOne> oneList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
DataModelOne dataModel = new DataModelOne();
dataModel.name = "name" + i;
dataModel.avatatColor = mColor[0];
oneList.add(dataModel);
}
List<DataModelTwo> twoList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
DataModelTwo dataModel = new DataModelTwo();
dataModel.name = "name" + i;
dataModel.avatatColor = mColor[1];
dataModel.content = "content:" + i;
twoList.add(dataModel);
}
List<DataModelThree> threeList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
DataModelThree dataModel = new DataModelThree();
dataModel.name = "name" + i;
dataModel.avatatColor = mColor[2];
dataModel.content = "content:" + i;
dataModel.contentColor = mColor[2];
threeList.add(dataModel);
}
mOneAdapter.addList(oneList,twoList,threeList);
mOneAdapter.notifyDataSetChanged();
}
3.3 改造后的Adapter
package com.materialdesignstudy.complexrecycler.itemthree;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.materialdesignstudy.R;
import com.materialdesignstudy.complexrecycler.itemone.DataModel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by HLQ on 2017/10/22
*/
public class TwoAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static final int TYPE_ONE = 1;
public static final int TYPE_TWO = 2;
public static final int TYPE_THREE = 3;
private LayoutInflater mLayoutInflater;
private List<Integer> mTypes = new ArrayList<>(); // 存放type
private Map<Integer, Integer> mPositions = new HashMap<>(); // 存放type下包含数据itemCount
private List<DataModelOne> mOneList = new ArrayList<>();
private List<DataModelTwo> mTwoList = new ArrayList<>();
private List<DataModelThree> mThreeList = new ArrayList<>();
public TwoAdapter(Context context) {
this.mLayoutInflater = LayoutInflater.from(context);
}
public void addList(List<DataModelOne> oneList, List<DataModelTwo> twoList, List<DataModelThree> threeList) {
addListByType(TYPE_ONE, oneList);
addListByType(TYPE_TWO, twoList);
addListByType(TYPE_THREE, threeList);
this.mOneList = oneList;
this.mTwoList = twoList;
this.mThreeList = threeList;
}
private void addListByType(int type, List list) {
mPositions.put(type, mTypes.size());
for (int i = 0; i < list.size(); i++) {
mTypes.add(type);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case DataModel.TYPE_ONE:
return new TypeOneViewHolder(mLayoutInflater.inflate(R.layout.item_type_one, parent, false));
case DataModel.TYPE_TWO:
return new TypeTwoViewHolder(mLayoutInflater.inflate(R.layout.item_type_two, parent, false));
case DataModel.TYPE_THREE:
return new TypeThreeViewHolder(mLayoutInflater.inflate(R.layout.item_type_three, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int viewType = getItemViewType(position);
int realPosition = position - mPositions.get(viewType);
switch (viewType) {
case DataModel.TYPE_ONE:
((TypeAbstractViewHolder) holder).bindHolder(mOneList.get(realPosition));
break;
case DataModel.TYPE_TWO:
((TypeAbstractViewHolder) holder).bindHolder(mTwoList.get(realPosition));
break;
case DataModel.TYPE_THREE:
((TypeAbstractViewHolder) holder).bindHolder(mThreeList.get(realPosition));
break;
}
}
@Override
public int getItemCount() {
return mTypes.size();
}
@Override
public int getItemViewType(int position) {
return mTypes.get(position);
}
}
到此,RecyclerView实现复杂布局结束了,还是希望大家有时间多看多敲,多去理解。
GitHub地址
学习地址
泥阿布慕课网视频地址:http://www.imooc.com/learn/731;
废大半天劲儿找到的GitHub地址:https://github.com/nimengbo
结束
我们沿着前人的路慢慢前行,只要坚持,就会有收获,可能目前的现状有些不尽人意,但是只要坚持下去,希望的曙光始终会照亮前行的路~
致自己,致坚持看完本文的你~加油~