1. 程式人生 > >Android框架之路——Banner實現輪播圖(RecyclerView新增Header)

Android框架之路——Banner實現輪播圖(RecyclerView新增Header)

一、簡介

Banner能實現迴圈播放多個廣告圖片和手動滑動迴圈等功能。因為原生ViewPager並不支援迴圈翻頁, 要實現迴圈還得需要自己去動手。Banner框架可以進行不同樣式、不同動畫設定, 以及完善的api方法能滿足大部分軟體首頁輪播圖效果的需求。

此專案的Github地址連結

本篇教程依然涉及一些其他方面的東西,如果有問題,可以參考我之前的系列文章。

二、新增依賴

  1. build.gradle中新增依賴:

    dependencies{
        compile 'com.youth.banner:banner:1.4.9'  //最新版本
    }
    
  2. 新增許可權:

    <!-- 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"/>
    
  • 實現效果:

四、Demo下載:

      原始碼下載


個人公眾號:每日推薦一篇技術部落格,堅持每日進步一丟丟…歡迎關注,想建個微信群,主要討論安卓和Java語言,一起打基礎、用框架、學設計模式,菜雞變菜鳥,菜鳥再起飛,願意一起努力的話可以公眾號留言,謝謝…