Android框架之路——Banner實現輪播圖(RecyclerView新增Header)
一、簡介
Banner能實現迴圈播放多個廣告圖片和手動滑動迴圈等功能。因為原生ViewPager並不支援迴圈翻頁, 要實現迴圈還得需要自己去動手。Banner框架可以進行不同樣式、不同動畫設定, 以及完善的api方法能滿足大部分軟體首頁輪播圖效果的需求。
此專案的Github地址:連結。
本篇教程依然涉及一些其他方面的東西,如果有問題,可以參考我之前的系列文章。
二、新增依賴
build.gradle中新增依賴:
dependencies{ compile 'com.youth.banner:banner:1.4.9' //最新版本 }
新增許可權:
<!-- if you want to load images from the internet --> <uses-permission android:name="android.permission.INTERNET" /> <!-- if you want to load images from a file OR from the internet --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
三、解鎖技能
1. 載入資料和重新整理資料:
首先,新增我們MainActivity的佈局檔案,裡面含有一個SwipeRefreshLayout,包裹RecyclerView,SwipeRefreshLayout用來實現下拉重新整理。
<?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="wrap_content" tools:context="com.ping.bannerdemo.activity.MainActivity"> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipeRefreshLayout" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content"> </android.support.v7.widget.RecyclerView> </android.support.v4.widget.SwipeRefreshLayout> </LinearLayout>
然後,我們要在RecyclerView中新增倆種佈局,一個Header存放Banner,另一個則是正常的item佈局。先放上倆個佈局的內容。
//header.xml <?xml version="1.0" encoding="utf-8"?> <com.youth.banner.Banner xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/banner" android:layout_width="match_parent" android:layout_height="match_parent"/> //item_recycler.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:padding="5dp" android:gravity="center" android:id="@+id/tv_item" android:textSize="20dp" android:textColor="#000" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
要新增倆種佈局,得在Adapter中實現,這裡可以全面的看一下各種LayoutManager中怎麼新增header佈局,戳這。這裡,我就用了文中的第一個,主要方法就是根據不同的ViewType返回不同的ViewHolder。通過setter方法將header的view注入進adapter。先來看一下adapter吧。
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { public static final int TYPE_HEADER = 0; public static final int TYPE_NORMAL = 1; private View mHeaderView; private String[] mData; private Context mContext; public void setHeaderView(View headerView) { mHeaderView = headerView; } public MyAdapter(Context context, String[] data) { mContext = context; mData = data; } //根據pos返回不同的ItemViewType @Override public int getItemViewType(int position) { if (mHeaderView == null) return TYPE_NORMAL; if (position == 0) return TYPE_HEADER; return TYPE_NORMAL; } //在此根據ItemViewType來決定返回何種ViewHolder @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mHeaderView != null && viewType == TYPE_HEADER) return new MyViewHolder(mHeaderView); View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler, parent, false); return new MyViewHolder(view); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { if(getItemViewType(position) == TYPE_HEADER) return; final int pos = getRealPosition(holder); final String data = mData[pos]; if(holder instanceof MyViewHolder) { ((MyViewHolder) holder).mTvItem.setText(data); } } private int getRealPosition(RecyclerView.ViewHolder holder) { int position = holder.getLayoutPosition(); return mHeaderView == null ? position : position - 1; } //返回正確的item個數 @Override public int getItemCount() { return mHeaderView == null ? mData.length : mData.length + 1; } public class MyViewHolder extends RecyclerView.ViewHolder { @BindView(R.id.tv_item) TextView mTvItem; public MyViewHolder(View itemView) { super(itemView); if(itemView == mHeaderView) return; ButterKnife.bind(this, itemView); } } }
看完Adapter,我們要看看在哪裡對headerview進行set了。MainActivity中我們渲染出header的佈局,然後通過setHeaderView將view傳遞進去即可。
//渲染header佈局 View header = LayoutInflater.from(this).inflate(R.layout.header, null); mBanner = (Banner) header.findViewById(R.id.banner); //設定banner的高度為手機螢幕的四分之一 mBanner.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, App.H/4)); //arrays資原始檔存放banner圖片的url地址 String[] data = getResources().getStringArray(R.array.demo_list); mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); myAdapter = new MyAdapter(this, data); //設定headerview myAdapter.setHeaderView(mBanner); mRecyclerView.setAdapter(myAdapter);
然而啟動banner:
mBanner.setImages(App.images) .setImageLoader(new GlideImageLoader()) .start();
這裡new了一個GlideImageLoader的例項,官方給出的各種圖片載入庫載入的方式如下:
public class GlideImageLoader extends ImageLoader { @Override public void displayImage(Context context, Object path, ImageView imageView) { /** 注意: 1.圖片載入器由自己選擇,這裡不限制,只是提供幾種使用方法 2.返回的圖片路徑為Object型別,由於不能確定你到底使用的那種圖片載入器, 傳輸的到的是什麼格式,那麼這種就使用Object接收和返回,你只需要強轉成你傳輸的型別就行, 切記不要胡亂強轉! */ eg: //Glide 載入圖片簡單用法 Glide.with(context).load(path).into(imageView); //Picasso 載入圖片簡單用法 Picasso.with(context).load(path).into(imageView); //用fresco載入圖片簡單用法,記得要寫下面的createImageView方法 Uri uri = Uri.parse((String) path); imageView.setImageURI(uri); } //提供createImageView 方法,如果不用可以不重寫這個方法,主要是方便自定義ImageView的建立 @Override public ImageView createImageView(Context context) { //使用fresco,需要建立它提供的ImageView,當然你也可以用自己自定義的具有圖片載入功能的ImageView SimpleDraweeView simpleDraweeView=new SimpleDraweeView(context); return simpleDraweeView; } }
下拉時,我們需要在輪播圖中更新資料,我們需要為SwipeRefreshLayout新增onRefresh()事件:
private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case REFRESH_COMPLETE: String[] urls = getResources().getStringArray(R.array.url); List list = Arrays.asList(urls); List arrayList = new ArrayList(list); //把新的圖片地址載入到Banner mBanner.update(arrayList); //下拉重新整理控制元件隱藏 mSwipeRefreshLayout.setRefreshing(false); break; } } }; ....... mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { mHandler.sendEmptyMessageDelayed(REFRESH_COMPLETE, 2000); } });
在Banner中,我們設定了高度為四分之一螢幕,螢幕的高度是在app一初始化就獲取到的,同時,在這裡我們用了一個Android Crash的框架Recovery,也沒必要用,就是可以把錯誤反饋到手機上。可以參考這個部落格。
public class App extends Application{ public static int H; public static List<?> images=new ArrayList<>(); public static List<String> titles=new ArrayList<>(); @Override public void onCreate() { super.onCreate(); initBanner(); } private void initBanner() { H = getScreenH(this); Recovery.getInstance() .debug(true) .recoverInBackground(false) .recoverStack(true) .mainPage(MainActivity.class) .init(this); String[] urls = getResources().getStringArray(R.array.url4); String[] tips = getResources().getStringArray(R.array.title); List list = Arrays.asList(urls); images = new ArrayList<>(list); titles= Arrays.asList(tips); } /** * 獲取螢幕高度 * @param aty * @return */ public int getScreenH(Context aty){ DisplayMetrics dm = aty.getResources().getDisplayMetrics(); return dm.heightPixels; } }
- 最後,看一下我們的執行效果:
2. Banner的各種動畫:
Banner的動畫都在com.youth.banner.transformer包下,包含下面的這些,相信看名字就知道大概什麼效果。
com.youth.banner.transformer.AccordionTransformer; com.youth.banner.transformer.BackgroundToForegroundTransformer; com.youth.banner.transformer.CubeInTransformer; com.youth.banner.transformer.CubeOutTransformer; com.youth.banner.transformer.DefaultTransformer; com.youth.banner.transformer.DepthPageTransformer; com.youth.banner.transformer.FlipHorizontalTransformer; com.youth.banner.transformer.FlipVerticalTransformer; com.youth.banner.transformer.ForegroundToBackgroundTransformer; com.youth.banner.transformer.RotateDownTransformer; com.youth.banner.transformer.RotateUpTransformer; com.youth.banner.transformer.ScaleInOutTransformer; com.youth.banner.transformer.StackTransformer; com.youth.banner.transformer.TabletTransformer; com.youth.banner.transformer.ZoomInTransformer; com.youth.banner.transformer.ZoomOutSlideTransformer; com.youth.banner.transformer.ZoomOutTranformer;
通過setBannerAnimation()來設定動畫。例如,設定為方塊滾動;
...... mBanner.setImages(App.images) .setBannerAnimation(CubeOutTransformer.class) .setImageLoader(new GlideImageLoader()) .start(); ......
效果如下:
3. Banner的各種內建樣式 :
Banner內建的樣式有以下幾種,
BannerConfig.NOT_INDICATOR //沒有指示器 BannerConfig.CIRCLE_INDICATOR //圓圈指示器(預設) BannerConfig.NUM_INDICATOR //數字圓盤指示器 BannerConfig.NUM_INDICATOR_TITLE //數字圓盤帶標題的 BannerConfig.CIRCLE_INDICATOR_TITLE //圓圈指示器附帶標題 BannerConfig.CIRCLE_INDICATOR_TITLE_INSIDE //區別於上一個,圓圈指示器在標題欄裡
這裡我們實現一下圓圈指示器附帶標題的樣式:
ArrayList<String> titles = new ArrayList<>(Arrays.asList(new String[]{"first title", "second title", "third title", "fourth title"})); mBanner.setImages(App.images) .setBannerAnimation(CubeOutTransformer.class) .setBannerStyle(BannerConfig.CIRCLE_INDICATOR_TITLE) .setBannerTitles(titles) .setImageLoader(new GlideImageLoader()) .start();
- 效果如下:
4. Banner自定義樣式:
自定義banner的屬性:
Attributes forma describe delay_time integer 輪播間隔時間,預設2000 scroll_time integer 輪播滑動執行時間,預設800 is_auto_play boolean 是否自動輪播,預設true title_background color reference title_textcolor color 標題字型顏色 title_textsize dimension 標題字型大小 title_height dimension 標題欄高度 indicator_width dimension 指示器圓形按鈕的寬度 indicator_height dimension 指示器圓形按鈕的高度 indicator_margin dimension 指示器之間的間距 indicator_drawable_selected reference 指示器選中效果 indicator_drawable_unselected reference 指示器未選中效果 image_scale_type enum 和imageview的ScaleType作用一樣 參照上述屬性,現在對指示器設定高寬和選中顏色:
<com.youth.banner.Banner xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/banner" android:layout_width="match_parent" android:layout_height="match_parent" app:indicator_drawable_selected="@color/colorAccent" app:indicator_drawable_unselected="@android:color/white" app:indicator_height="4dp" app:indicator_margin="4dp" app:indicator_width="20dp"/>
- 實現效果: