关于BaseHolder的介绍,大家可以看这篇文章对BaseAdapter和ViewHolder的封装
想必大家一定会问,BaseAdapter和ViewHolder不是用来实现对ListView列表展示数据的优化吗,跟今天要说的展示数据有什么关系?
没错,BaseAdapter确实和今天要说的内容没有什么关系,但是ViewHolder就有关系了,这个东西不但可以用来优化ListView的数据展示,还可以用来展示其他任何你想要的数据和布局.
为什么说ViewHolder可以用来展示任何数据和布局呢?使用这种方式有什么优点呢?
我这里所说的ViewHolder是只已经封装过的BaseHolder,它是一个抽象类,该类定义了2个抽象方法:
1. public abstract View initView();
由子类实现,初始化布局
2.public abstract void refreshView(T t);
由子类实现,将数据填充在布局上.
但凡操作界面,无非就这2步骤,初始化布局,将数据填充在布局上,竟然如此,我们何不把这些常规的操作通过面向对象的方式交个一个类去处理,我们只需要把数据塞给它,交由它把数据填充到布局上,最后再把已经填充好数据的布局返回给我们,我们直接拿去展示就好了,这就好比似购物一样,一手交钱一手交货.这样一来,我们的Activity和Fragment上面的代码就会减少很多.也方便我们维护和查阅.
上面说到的这个类,也就是这里所介绍的BaseHolder,在使用的过程中,需要新建它的子类,实现其2个抽象方法,然后在Activity或者Fragment上要用的时候,就只需要4个步骤:
1.创建BaseHolder子类的实例;
2.通过改实例调用BaseHolder的公共方法setData(T t) 将数据传递给BaseHolder;
3.通过改实例调用BaseHolder的公共方法getConvertView()获取已经填充好数据的布局.
4.拿到布局后就可以执行展示布局的逻辑了.
下面分别介绍上面前3个步骤的都做了那些细节:
1.创建BaseHolder子类的实例,这一步是为了执行该实例的父类构造方法,也即是BaseHolder的构造方法,因为在这个基类的构造方法中,会调用initView()抽象方法,该方法被调用,所有实现它的子类的initView()方法都会执行.这个时候相当于布局初始化了.
2.调用setData(T t)公共 方法,该方法一旦执行,数据就会由Activity/Fragment中传递进来并持有.并且,setData(T t)方法还会调用refreshView()抽象方法,那么所有实现它的子类的refreshView(T t)方法都会执行,这个时候相当于填充数据到界面.同时由于setData(T t)方法接收的数据类型是一个泛型,那么在调用的时候就可以确定数据的类型了.
3.调用getConvertView()公共方法,该方法返回的View是布局的根View,这个根View也就是initView()抽象方法的返回值.
而根View里面的所有子View的数据填充,经过步骤2就已经完成了.所以在步骤3的时候拿到的根View是可以直接展示在界面上的了.
好了,分析了原理,我们就来看看代码如何操作.今天就用BaseHolder来实现下面的焦点图效果:
这个焦点图很常见,通常展示在ListView的头部,或者嵌套在ScrollView的内部等等.
代码如下:
MainActivity
public class MainActivity extends AppCompatActivity {
private List<String> mData;//ListView列表的数据
private List<String> mHeaderData;//焦点图的url集合
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//初始化数据
initData();
//创建ListView
ListView listView = new ListView(this);
//创建处理头部ViewPager的BaseHolder子类
HomeHeaderHolder headerHolder = new HomeHeaderHolder();
//设置头部数据
headerHolder.setData(mHeaderData);
//将填充好头部数据的布局添加到ListView的头部
listView.addHeaderView(headerHolder.getConvertView());
//设置适配器
listView.setAdapter(new HomeAdapter(mData));
setContentView(listView);
}
/**
* 初始化数据
*/
private void initData() {
mData = new ArrayList<>();
mHeaderData = new ArrayList<>();
for (int i = 0; i < 20; i++) {
mData.add("测试数据:" + i);
}
for (int i = 1; i <= 8; i++) {
mHeaderData.add("http://127.0.0.1:8090/image?name=" + "image/home0" + i + ".jpg");
}
}
/**
* 自定义ListView适配器
*/
private class HomeAdapter extends MyBaseAdapter<String> {
public HomeAdapter(List<String> data) {
super(data);
}
@Override
public BaseHolder getBaseHolder(int position) {
return new BaseHolder<String>() {
private TextView tvInfo;
@Override
public void refreshView(String s) {
tvInfo.setText(s);
}
@Override
public View initView() {
tvInfo = new TextView(MainActivity.this);
tvInfo.setPadding(10, 10, 10, 10);
tvInfo.setGravity(Gravity.CENTER_VERTICAL);
return tvInfo;
}
};
}
}
}
上面的代码加上注释和空行总共就63行代码,如果再将ListView的Adapter的代码提取到其他包的话,Activity的代码就更少了.同时,我们也可以看到ListView的定义Adapter的代码也很少,关于这部分的优化可以看这篇文章对BaseAdapter和ViewHolder的封装,这里主要介绍BaseHolder的扩展使用.
我们都知道如果把实现焦点图的代码逻辑统统都写在Activity的话,那么Activity的代码将会变得很庞大,这样非常不利于我们维护和阅读.
接着HomeHeaderHolder这个类
/**
* Created by mChenys on 2015/11/22.
*/
public class HomeHeaderHolder extends BaseHolder<List<String>> {
private ViewPager mViewPager;
private BitmapUtils mBitmapUtils;//XUtils的图片加载库
private LinearLayout mIndicatorLl;//ViewPager的指示点的根布局
private int mPreviousPos;// 上一个指示点位置
private AutoPlayRunnable mAutoPlayRunnable;//ViewPager自动轮播焦点图的Runnable对象
private Handler mHandler;//处理自动mAutoPlayRunnable的Handler
public HomeHeaderHolder() {
super();
mHandler = new Handler();
mBitmapUtils = new BitmapUtils(ContextUtils.getContext());
}
@Override
public void refreshView(final List<String> urls) {
//设置适配器
mViewPager.setAdapter(new PagerAdapter() {
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
position = position % getData().size();
ImageView imageView = new ImageView(ContextUtils.getContext());
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);// 设置缩放模式,居中裁剪
mBitmapUtils.display(imageView, urls.get(position));
container.addView(imageView);
return imageView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
});
//设置ViewPager当前展示的位置
mViewPager.setCurrentItem(urls.size() * 1000);
//创建指示器圆点
if (null != urls && urls.size() > 0) {
for (int i = 0; i < urls.size(); i++) {
ImageView imageDot = new ImageView(ContextUtils.getContext());
LinearLayout.LayoutParams dotLp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
if (i == 0) {
//将第一个点设置为选中状态
imageDot.setImageResource(R.drawable.indicator_selected);
} else {
dotLp.leftMargin = 5;//左边距
imageDot.setImageResource(R.drawable.indicator_normal);
}
mIndicatorLl.addView(imageDot, dotLp);
}
}
//设置ViewPager的滑动监听
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
//当前位置
position = position % getData().size();
ImageView currDot = (ImageView) mIndicatorLl.getChildAt(position);
//将当前显示的位置的圆点变成selected
currDot.setImageResource(R.drawable.indicator_selected);
//将上一次被选中的位置重置为normal
ImageView preDot = (ImageView) mIndicatorLl.getChildAt(mPreviousPos);
preDot.setImageResource(R.drawable.indicator_normal);
//更新上一次的位置
mPreviousPos = position;
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
//实现自动轮播的效果
mAutoPlayRunnable = new AutoPlayRunnable();
mAutoPlayRunnable.start();
//viewPager手指点击的时候需要停止轮播,弹起的时候继续轮播
mViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
mAutoPlayRunnable.stop();
break;
case MotionEvent.ACTION_UP:
mAutoPlayRunnable.start();
}
return false;
}
});
}
@Override
public View initView() {
//创建根布局
RelativeLayout root = new RelativeLayout(ContextUtils.getContext());
root.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, SizeUtils.getDimen(R.dimen.home_list_header)));
//创建ViewPager
mViewPager = new ViewPager(ContextUtils.getContext());
root.addView(mViewPager, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
//创建原点指示器, 在相对布局的右下角在展示
mIndicatorLl = new LinearLayout(ContextUtils.getContext());
mIndicatorLl.setOrientation(LinearLayout.HORIZONTAL);
RelativeLayout.LayoutParams indicatorLp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
indicatorLp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);// 下方
indicatorLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);// 右侧
// 设置内边距
mIndicatorLl.setPadding(5, 5, 5, 5);
root.addView(mIndicatorLl, indicatorLp);
return root;
}
/**
* 轮播图的Runnable
*/
class AutoPlayRunnable implements Runnable {
/**
* 开启轮播
*/
public void start() {
//每次启动,先清空所有的消息和runable,避免发送重复的
mHandler.removeCallbacksAndMessages(null);
//延时启动
mHandler.postDelayed(this, 3000);
}
/**
* 暂停轮播
*/
public void stop() {
mHandler.removeCallbacksAndMessages(null);
}
@Override
public void run() {
//获取当前显示的位置
int currPosition = mViewPager.getCurrentItem();
//切换到下一张图片
currPosition++;
if (currPosition > Integer.MAX_VALUE) {
currPosition = 0;
} else if (currPosition < 0) {
currPosition = Integer.MAX_VALUE;
}
mViewPager.setCurrentItem(currPosition);
//执行下一次循环
mHandler.postDelayed(this, 3000);
}
}
}
上面的代码总共169行,通过这种方式将大大减小了Activity的体积.这就是面向对象的好处.
今天刚好遇上广州最冷的一次寒潮,手指要被冻僵的感觉~~