关于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的体积.这就是面向对象的好处.
今天刚好遇上广州最冷的一次寒潮,手指要被冻僵的感觉~~
源码下载