]Android 仿网易新闻 ViewPager 实现图片自动轮播

hcql1463 8年前

来自: http://blog.csdn.net//never_cxb/article/details/50491558


前言

新闻 App 首页最上方一般会循环播放热点图片,如下图所示。

http://blog.csdn.net/never_cxb

本文主要介绍了利用 ViewPager 实现轮播图片,图片下方加上小圆点指示器标记当前位置,并利用 Timer+Handler 实现了自动轮播播放。

本文链接 http://blog.csdn.net/never_cxb/article/details/50515216 转载请注明出处

xml 布局

<?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">        <!--ViewPager 热门文章图片展示-->      <FrameLayout  android:layout_width="match_parent" android:layout_height="200dp" android:background="@color/gray_light">            <android.support.v4.view.ViewPager  android:id="@+id/vp_hottest" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimary" />            <LinearLayout  android:id="@+id/ll_hottest_indicator" android:layout_width="wrap_content" android:layout_height="20dp" android:layout_gravity="bottom|right" android:layout_marginBottom="5dp" android:layout_marginRight="10dp" android:layout_marginTop="5dp" android:gravity="center" android:orientation="horizontal" />      </FrameLayout>    </LinearLayout>

FrameLayout里面包含了ViewPager和LinearLayout,ViewPager 显示图片,LinearLayout是小圆点指示器区域,标记现在滑到哪张图片。

查看 xml 预览图,由于没有图片内容,当前只显示出红色矩形区域。

http://blog.csdn.net/never_cxb

新建javabean

首页的图片地址是新闻的一个属性,我们新建一个ItemArticle类。

public class ItemArticle {      // 新闻的 id      private int index;      // 新闻里的图片 url      private String imageUrl;        public ItemArticle(int index, String imageUrl) {          this.index = index;          this.imageUrl = imageUrl;      }        public int getIndex() {          return index;      }        public void setIndex(int index) {          this.index = index;      }        public String getImageUrl() {          return imageUrl;      }        public void setImageUrl(String imageUrl) {          this.imageUrl = imageUrl;      }  }

适配器 PagerAdapter

继承自 android.support.v4.view.PagerAdapter,复写4个方法

  • instantiateItem(ViewGroup, int)
  • destroyItem(ViewGroup, int, Object)
  • getCount()
  • isViewFromObject(View, Object)
public class HeaderAdapter extends PagerAdapter {      private static final String LOG = "NEWS_LOG";        private Activity context;      private List<ItemArticle> articles;      private List<SimpleDraweeView> images = new ArrayList<SimpleDraweeView>();          public HeaderAdapter(Activity context, List<ItemArticle> articles) {          this.context = context;          if (articles == null || articles.size() == 0) {              this.articles = new ArrayList<>();          } else {              this.articles = articles;          }            for (int i = 0; i < articles.size(); i++) {              SimpleDraweeView image = new SimpleDraweeView(context);              Uri uri = Uri.parse(articles.get(i).getImageUrl());              image.setImageURI(uri);              images.add(image);          }      }        @Override      public Object instantiateItem(ViewGroup container, int position) {          container.addView(images.get(position));          return images.get(position);      }        @Override      public void destroyItem(ViewGroup container, int position, Object object) {          container.removeView(images.get(position));      }        @Override      public int getCount() {          return articles.size();      }        @Override      public boolean isViewFromObject(View view, Object object) {          Log.i(LOG, "in isViewFromObject view: " + view + " object: "                  + object + " equal: " + (view == (View) object));          return view == (View) object;      }  }

深入解析 isViewFromObject 方法

isViewFromObject(View view, Object object)的通用写法是return view == (View) object;
其中(View) object可根据具体情形替换成LinearLayout等等。

查看 ViewPager 源代码(戳这里

isViewFromObject是在infoForChild里被调用的,而且在该方法内会被调用mItems.size()次,mItems.size()是 ViewPager 里面图片的个数。

static class ItemInfo {      Object object;      int position;      boolean scrolling;  }    private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();    ItemInfo infoForChild(View child) {      for (int i=0; i<mItems.size(); i++) {          ItemInfo ii = mItems.get(i);          if (mAdapter.isViewFromObject(child, ii.object)) {              return ii;          }      }      return null;  }

ViewPager里面用了一个mItems 存储每个page的信息(ItemInfo),当界面要展示或者发生变化时,需要依据page的当前信息来调整,但此时只能通过view来查找,遍历mItems通过比较view和object来找到对应的ItemInfo。

Log.i(LOG, "in isViewFromObject view: " + view + " object: "          + object + " equal: " + (view == (View) object));

所以我们如果打印出 Log 的话,会看到isViewFromObject()被调用多次,只有1次返回 true (表示找到了对应的ItemInfo),其他返回 false。

01-17 10:15:21.207 I/NEWS_LOG﹕ in isViewFromObject   view: SimpleDraweeView{holder=DraweeHolder{...}   object: SimpleDraweeView{holder=DraweeHolder{...}   equal: false    01-17 10:15:21.207 I/NEWS_LOG﹕ in isViewFromObject   view: SimpleDraweeView{holder=DraweeHolder{...}   object: SimpleDraweeView{holder=DraweeHolder{...}   equal: false    01-17 10:15:21.207 I/NEWS_LOG﹕ in isViewFromObject   view: SimpleDraweeView{holder=DraweeHolder{...}   object: SimpleDraweeView{holder=DraweeHolder{...}   equal: false    01-17 10:15:21.207 I/NEWS_LOG﹕ in isViewFromObject   view: SimpleDraweeView{holder=DraweeHolder{...}   object: SimpleDraweeView{....}}   equal: true

增加底部小圆点指示器

轮播图片的底部都会加上小圆点,指示当前访问图片的位置。

http://blog.csdn.net/never_cxb

private ImageView[] mBottomImages;//底部只是当前页面的小圆点      //创建底部指示位置的导航栏  mBottomImages = new ImageView[headerArticles.size()];    for (int i = 0; i < mBottomImages.length; i++) {      ImageView imageView = new ImageView(mAct);      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(10, 10);      params.setMargins(5, 0, 5, 0);      imageView.setLayoutParams(params);      if (i == 0) {          imageView.setBackgroundResource(R.drawable.indicator_select);      } else {          imageView.setBackgroundResource(R.drawable.indicator_not_select);      }        mBottomImages[i] = imageView;      //把指示作用的原点图片加入底部的视图中      llHottestIndicator.addView(mBottomImages[i]);    }

上面这段代码是小圆点的初始步骤,最开始是第0张图片被选中,所以是第0张小圆点是蓝色,其他小圆点是灰色。

addOnPageChangeListener 使得小圆点动态变化

切换图片的时候,小圆点也要随着改变,这需要利用ViewPager.OnPageChangeListener,主要是下面这个方法:

public abstract void onPageSelected (int position)

This method will be invoked when a new page becomes selected. Animation is not necessarily complete.    Parameters  position    Position index of the new selected page.
vpHottest.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {          //图片左右滑动时候,将当前页的圆点图片设为选中状态      @Override      public void onPageSelected(int position) {          // 一定几个图片,几个圆点,但注意是从0开始的          int total = mBottomImages.length;          for (int j = 0; j < total; j++) {              if (j == position) {                  mBottomImages[j].setBackgroundResource(R.drawable.indicator_select);              } else {                  mBottomImages[j].setBackgroundResource(R.drawable.indicator_not_select);              }          }      }        @Override      public void onPageScrolled(int i, float v, int i1) {          }        @Override      public void onPageScrollStateChanged(int state) {      }  });

onPageSelected()中,利用 for 循环,将当前选中位置对应的小圆点置为蓝色,其他小圆点置为灰色。

自动播放

先定义一个 Handler,在主线程里面更新 UI

//定时轮播图片,需要在主线程里面修改 UI  private Handler mHandler = new Handler() {      public void handleMessage(Message msg) {          switch (msg.what) {              case UPTATE_VIEWPAGER:                  if (msg.arg1 != 0) {                      vpHottest.setCurrentItem(msg.arg1);                  } else {                      //false 当从末页调到首页是,不显示翻页动画效果,                      vpHottest.setCurrentItem(msg.arg1, false);                  }                  break;          }      }  };

利用 Timer 实现每隔 5s 向 Handler 发送message来更新图片

// 设置自动轮播图片,5s后执行,周期是5s  timer.schedule(new TimerTask() {      @Override      public void run() {          Message message = new Message();          message.what = UPTATE_VIEWPAGER;          if (autoCurrIndex == headerArticles.size() - 1) {              autoCurrIndex = -1;          }          message.arg1 = autoCurrIndex + 1;          mHandler.sendMessage(message);      }  }, 5000, 5000);

为了使得滑到最后一页后能滑到首页,我们对于autoCurrIndex == headerArticles.size() - 1进行了处理。

完整代码

基于上面的分析,我们实现了自动轮播图片

http://blog.csdn.net/never_cxb

public class MasterArticleFragment extends Fragment {        private static final String ARTICLE_LATEST_PARAM = "param";        private static final int UPTATE_VIEWPAGER = 0;        //轮播的最热新闻图片      @InjectView(R.id.vp_hottest)      ViewPager vpHottest;      //轮播图片下面的小圆点      @InjectView(R.id.ll_hottest_indicator)      LinearLayout llHottestIndicator;        //存储的参数      private String mParam;        //获取 fragment 依赖的 Activity,方便使用 Context      private Activity mAct;        //设置当前 第几个图片 被选中      private int autoCurrIndex = 0;        private ImageView[] mBottomImages;//底部只是当前页面的小圆点         private Timer timer = new Timer(); //为了方便取消定时轮播,将 Timer 设为全局        //定时轮播图片,需要在主线程里面修改 UI      private Handler mHandler = new Handler() {          public void handleMessage(Message msg) {              switch (msg.what) {                  case UPTATE_VIEWPAGER:                      if (msg.arg1 != 0) {                          vpHottest.setCurrentItem(msg.arg1);                      } else {                          //false 当从末页调到首页是,不显示翻页动画效果,                          vpHottest.setCurrentItem(msg.arg1, false);                      }                      break;              }          }      };        public static MasterArticleFragment newInstance(String param) {          MasterArticleFragment fragment = new MasterArticleFragment();          Bundle args = new Bundle();          args.putString(ARTICLE_LATEST_PARAM, param);          fragment.setArguments(args);          return fragment;      }        @Override      public void onCreate(@Nullable Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          if (savedInstanceState != null) {              mParam = savedInstanceState.getString(ARTICLE_LATEST_PARAM);          }      }        @Nullable      @Override      public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {            View view = inflater.inflate(R.layout.fragment_one_master, container, false);          mAct = getActivity();          ButterKnife.inject(this, view);          return view;      }        @Override      public void onActivityCreated(@Nullable Bundle savedInstanceState) {          super.onActivityCreated(savedInstanceState);          new ImageTask().execute();      }        @Override      public void onDestroyView() {          super.onDestroyView();          ButterKnife.reset(this);      }          private void setUpViewPager(final List<ItemArticle> headerArticles) {          HeaderAdapter imageAdapter = new HeaderAdapter(mAct, headerArticles);          vpHottest.setAdapter(imageAdapter);            //创建底部指示位置的导航栏          mBottomImages = new ImageView[headerArticles.size()];            for (int i = 0; i < mBottomImages.length; i++) {              ImageView imageView = new ImageView(mAct);              LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(10, 10);              params.setMargins(5, 0, 5, 0);              imageView.setLayoutParams(params);              if (i == 0) {                  imageView.setBackgroundResource(R.drawable.indicator_select);              } else {                  imageView.setBackgroundResource(R.drawable.indicator_not_select);              }                mBottomImages[i] = imageView;              //把指示作用的原点图片加入底部的视图中              llHottestIndicator.addView(mBottomImages[i]);            }            vpHottest.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {                  //图片左右滑动时候,将当前页的圆点图片设为选中状态                @Override                public void onPageSelected(int position) {                    // 一定几个图片,几个圆点,但注意是从0开始的                    int total = mBottomImages.length;                    for (int j = 0; j < total; j++) {                        if (j == position) {                            mBottomImages[j].setBackgroundResource(R.drawable.indicator_select);                        } else {                            mBottomImages[j].setBackgroundResource(R.drawable.indicator_not_select);                        }                    }                      //设置全局变量,currentIndex为选中图标的 index                    autoCurrIndex = position;                }                  @Override                public void onPageScrolled(int i, float v, int i1) {                }                  @Override                public void onPageScrollStateChanged(int state) {                }            }          );            // 设置自动轮播图片,5s后执行,周期是5s          timer.schedule(new TimerTask() {              @Override              public void run() {                  Message message = new Message();                  message.what = UPTATE_VIEWPAGER;                  if (autoCurrIndex == headerArticles.size() - 1) {                      autoCurrIndex = -1;                  }                  message.arg1 = autoCurrIndex + 1;                  mHandler.sendMessage(message);              }          }, 5000, 5000);      }          class ImageTask extends AsyncTask<String, Void, List<ItemArticle>> {          @Override          protected List<ItemArticle> doInBackground(String... params) {              List<ItemArticle> articles = new ArrayList<ItemArticle>();              articles.add(                      new ItemArticle(1123, "http://***20151231105648_11790.jpg"));              articles.add(                      new ItemArticle(1123, "http://***20151230152544_36663.jpg"));              articles.add(                      new ItemArticle(1123, "http://***20151229204329_75030.jpg"));              articles.add(                      new ItemArticle(1123, "http://***20151221151031_36136.jpg"));              return articles;          }            @Override          protected void onPostExecute(List<ItemArticle> articles) {              //这儿的 是 url 的集合              super.onPostExecute(articles);              setUpViewPager(articles);            }      }  }

一些知识点

schedule和scheduleAtFixedRate方法

(1)schedule方法:下一次执行时间相对于 上一次 实际执行完成的时间点 ,因此执行时间会不断延后。保持间隔时间的稳定
(2)scheduleAtFixedRate方法:下一次执行时间相对于上一次开始的 时间点 ,因此执行时间不会延后,存在并发性 。保持执行频率的稳定。

参考文章