]Android 仿網易新聞 ViewPager 實現圖片自動輪播
來自: http://blog.csdn.net//never_cxb/article/details/50491558
前言
新聞 App 首頁最上方一般會循環播放熱點圖片,如下圖所示。
本文主要介紹了利用 ViewPager 實現輪播圖片,圖片下方加上小圓點指示器標記當前位置,并利用 Timer+Handler 實現了自動輪播播放。
本文鏈接 http://blog.csdn.net/never_cxb/article/details/50515216 轉載請注明出處
xml 布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="<!--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></pre>
FrameLayout里面包含了ViewPager和LinearLayout,ViewPager 顯示圖片,LinearLayout是小圓點指示器區域,標記現在滑到哪張圖片。
查看 xml 預覽圖,由于沒有圖片內容,當前只顯示出紅色矩形區域。
新建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; }
}</pre>
適配器 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; }
}</pre>
深入解析 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; }</pre>
ViewPager里面用了一個mItems 存儲每個page的信息(ItemInfo),當界面要展示或者發生變化時,需要依據page的當前信息來調整,但此時只能通過view來查找,遍歷mItems通過比較view和object來找到對應的ItemInfo。
Log.i(LOG, "in isViewFromObject view: " + view + " object: "+ object + " equal: " + (view == (View) object));</pre>
所以我們如果打印出 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: false01-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</pre>
增加底部小圓點指示器
輪播圖片的底部都會加上小圓點,指示當前訪問圖片的位置。
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]);
}</pre>
上面這段代碼是小圓點的初始步驟,最開始是第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.</pre>
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) { }
});</pre>
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
進行了處理。完整代碼
基于上面的分析,我們實現了自動輪播圖片
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); } }
}</pre>
一些知識點
schedule和scheduleAtFixedRate方法
(1)schedule方法:下一次執行時間相對于 上一次 實際執行完成的時間點 ,因此執行時間會不斷延后。保持間隔時間的穩定
(2)scheduleAtFixedRate方法:下一次執行時間相對于上一次開始的 時間點 ,因此執行時間不會延后,存在并發性 。保持執行頻率的穩定。參考文章
LoremPixel 圖片資源網站,隨機生成圖片 http://lorempixel.com/
Android Image Slideshow using ViewPager with PagerAdapter http://codetheory.in/android-image-slideshow-using-viewpager-pageradapter/
安卓PagerAdapter中的isViewFromObject()方法有什么用? http://segmentfault.com/q/1010000000484617
Timer的schedule和scheduleAtFixedRate方法的區別解析 http://blog.csdn.net/gtuu0123/article/details/6040159
Android 仿首頁廣告輪播效果 http://blog.csdn.net/xiyou_android/article/details/45566129
Android Material Design之Toolbar與Palette實踐 http://blog.csdn.net/bbld_/article/details/41439715
</ul> </div>