【Android】造輪子:輪播圖

一杯倒 8年前發布 | 6K 次閱讀 安卓開發 Android開發 移動開發

前言

目前市場上的APP中,輪播圖可以說是很常見的。一個好的輪播圖,基本上適用于所有的APP。是時候打造一個自己的輪播圖了,不要等到用的時候才去Google。

功能

輪播圖需要實現一下功能

  • 圖片循環輪播
  • 可添加文字
  • 最后一張到第一張的切換也要有切換效果
  • 循環、自動播放可控制

還有我們都比較關注的一點: 這輪子必須易拆、易裝,可擴展性強 。每次換個項目就要拷貝好幾個文件,改一大堆代碼,這是很煩的。

實現

再多的文字也不如一張圖來得直觀,先來個福利,回頭再說怎實現的。

效果

思路

這里使用 ViewPager 來實現輪播的效果,但是 ViewPager 是滑動到最后一張時,是不能跳轉到第一張的。于是,我們可以這樣:

  • 需要顯示的輪播圖有N張
  • 往 ViewPager 中添加 N 個 View ,這時 ViewPager 中有:
    View(1)、View(2)、View(3) ... View(N)
  • 再往 ViewPager 中添加 View(1) ,這時 ViewPager 中有:
    View(1)、View(2)、View(3) ... View(N)、View(1)

這樣就可以實現一種視覺效果:滑動到最后一張 View(N) 的時候,再往后滑動就回到了第一張 View(1) 。

這也適用于從第一張條轉到最后一張的實現。

文字看著費解?那就看圖吧(還好會那么一點點PS)

例:

需要顯示三張圖:

需要輪播的圖片

經過處理,變成這樣

處理后的輪播圖

在界面上看到的是三張圖片,而實際在ViewPager中的是這樣的5張。

  • 當從View4跳轉到View5時,在代碼中立刻將視圖切換到View2,應為圖片是一樣的,所有在界面上看不到任何效果。
  • 同理,當從View2跳轉到View1時,在代碼中將視圖切換到View4。

自動輪播流程:

View2 --> View3 --> View4 --> View5 --> View2 (完成一次循環)--> View3 --> View4 ....

當顯示 View5 的時候,立刻切換到 View2 ( View5 和 View2 顯示的內容是相同的),這樣就實現了圖片輪播。

這里 View5 -> View2 的切換巧妙利用了 ViewPager 中的方法:

setCurrentItem(int item, boolean smoothScroll)

參數 smoothScroll為false 的時候,實現了“看不見”的跳轉。

還是不大清楚?那就直接看代碼吧

代碼

思路說完,上代碼

  • 創建model

    這里創建一個 Info 類,模擬實際應用中的數據。里面有 title 和 url 字段。

    public class Info {
      private String url;
      private String title;

    public Info(String title, String url) { this.url = url; this.title = title; }

    public String getUrl() { return url; }

    public void setUrl(String url) { this.url = url; }

    public String getTitle() { return title; }

    public void setTitle(String title) { this.title = title; } }</code></pre> </li>

  • 布局
    為了實現畫面重疊的效果,這里用了相對布局,輪播圖使用 ViewPager 來實現。后面有兩個 LinearLayout ,第一個 LinearLayout 用來放指示器,在 java 代碼中動態添加;第二個 LinearLayout 就用來顯示 Title 了,當然,如果還需要顯示的其他內容,可以在這個布局里面中添加。

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@android:color/white"
      android:orientation="vertical">
      <android.support.v4.view.ViewPager
          android:id="@+id/cycle_view_pager"
          android:layout_width="match_parent"
          android:layout_height="match_parent" />
      <LinearLayout
          android:id="@+id/cycle_indicator"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_alignParentBottom="true"
          android:layout_marginBottom="10dp"
          android:gravity="center"
          android:orientation="horizontal" />
      <LinearLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_above="@id/cycle_indicator"
          android:orientation="vertical">
          <TextView
              android:id="@+id/cycle_title"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginBottom="10dp"
              android:gravity="center"
              android:textColor="@android:color/white"
              android:textSize="20sp" />
      </LinearLayout>
    </RelativeLayout>
  • CycleViewPager

    重點來了,自定義的輪播圖。來個重磅炸彈,別看暈了

    public class CycleViewPager extends FrameLayout implements ViewPager.OnPageChangeListener {

    private static final String TAG = "CycleViewPager";

    private Context mContext;

    private ViewPager mViewPager;//實現輪播圖的ViewPager

    private TextView mTitle;//標題

    private LinearLayout mIndicatorLayout; // 指示器

    private Handler handler;//每幾秒后執行下一張的切換

    private int WHEEL = 100; // 轉動

    private int WHEEL_WAIT = 101; // 等待

    private List<View> mViews = new ArrayList<>(); //需要輪播的View,數量為輪播圖數量+2

    private ImageView[] mIndicators; //指示器小圓點

    private boolean isScrolling = false; // 滾動框是否滾動著

    private boolean isCycle = true; // 是否循環,默認為true

    private boolean isWheel = true; // 是否輪播,默認為true

    private int delay = 4000; // 默認輪播時間

    private int mCurrentPosition = 0; // 輪播當前位置

    private long releaseTime = 0; // 手指松開、頁面不滾動時間,防止手機松開后短時間進行切換

    private ViewPagerAdapter mAdapter;

    private ImageCycleViewListener mImageCycleViewListener;

    private List<Info> infos;//數據集合

    private int mIndicatorSelected;//指示器圖片,被選擇狀態

    private int mIndicatorUnselected;//指示器圖片,未被選擇狀態

    final Runnable runnable = new Runnable() { @Override public void run() { if (mContext != null && isWheel) { long now = System.currentTimeMillis(); // 檢測上一次滑動時間與本次之間是否有觸擊(手滑動)操作,有的話等待下次輪播 if (now - releaseTime > delay - 500) { handler.sendEmptyMessage(WHEEL); } else { handler.sendEmptyMessage(WHEEL_WAIT); } } } };

    public CycleViewPager(Context context) { this(context, null); }

    public CycleViewPager(Context context, AttributeSet attrs) { this(context, attrs, 0); }

    public CycleViewPager(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; initView(); }

    /**

    • 初始化View */ private void initView() { LayoutInflater.from(mContext).inflate(R.layout.layout_cycle_view, this, true); mViewPager = (ViewPager) findViewById(R.id.cycle_view_pager); mTitle = (TextView) findViewById(R.id.cycle_title); mIndicatorLayout = (LinearLayout) findViewById(R.id.cycle_indicator); handler = new Handler() {

       @Override
       public void handleMessage(Message msg) {
           super.handleMessage(msg);
           if (msg.what == WHEEL && mViews.size() > 0) {
               if (!isScrolling) {
                   //當前為非滾動狀態,切換到下一頁
                   int posttion = (mCurrentPosition + 1) % mViews.size();
                   mViewPager.setCurrentItem(posttion, true);
               }
               releaseTime = System.currentTimeMillis();
               handler.removeCallbacks(runnable);
               handler.postDelayed(runnable, delay);
               return;
           }
           if (msg.what == WHEEL_WAIT && mViews.size() > 0) {
               handler.removeCallbacks(runnable);
               handler.postDelayed(runnable, delay);
           }
       }
      

      }; }

      /**

    • 設置指示器圖片,在setData之前調用 *
    • @param select 選中時的圖片
    • @param unselect 未選中時的圖片 */ public void setIndicators(int select, int unselect) { mIndicatorSelected = select; mIndicatorUnselected = unselect; } public void setData(List<Info> list, ImageCycleViewListener listener) { setData(list, listener, 0); }

      /**

    • 初始化viewpager *
    • @param list 要顯示的數據
    • @param showPosition 默認顯示位置 */ public void setData(List<Info> list, ImageCycleViewListener listener, int showPosition) { if (list == null || list.size() == 0) {

       //沒有數據時隱藏整個布局
       this.setVisibility(View.GONE);
       return;
      

      } mViews.clear(); infos = list; if (isCycle) {

       //添加輪播圖View,數量為集合數+2
       // 將最后一個View添加進來
       mViews.add(getImageView(mContext, infos.get(infos.size() - 1).getUrl()));
       for (int i = 0; i < infos.size(); i++) {
           mViews.add(getImageView(mContext, infos.get(i).getUrl()));
       }
       // 將第一個View添加進來
       mViews.add(getImageView(mContext, infos.get(0).getUrl()));
      

      } else {

       //只添加對應數量的View
       for (int i = 0; i < infos.size(); i++) {
           mViews.add(getImageView(mContext, infos.get(i).getUrl()));
       }
      

      } if (mViews == null || mViews.size() == 0) {

       //沒有View時隱藏整個布局
       this.setVisibility(View.GONE);
       return;
      

      } mImageCycleViewListener = listener; int ivSize = mViews.size(); // 設置指示器 mIndicators = new ImageView[ivSize]; if (isCycle)

       mIndicators = new ImageView[ivSize - 2];
      

      mIndicatorLayout.removeAllViews(); for (int i = 0; i < mIndicators.length; i++) {

       mIndicators[i] = new ImageView(mContext);
       LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
               LinearLayout.LayoutParams.WRAP_CONTENT, 
               LinearLayout.LayoutParams.WRAP_CONTENT);
       lp.setMargins(10, 0, 10, 0);
       mIndicators[i].setLayoutParams(lp);
       mIndicatorLayout.addView(mIndicators[i]);
      

      } mAdapter = new ViewPagerAdapter(); // 默認指向第一項,下方viewPager.setCurrentItem將觸發重新計算指示器指向 setIndicator(0); mViewPager.setOffscreenPageLimit(3); mViewPager.setOnPageChangeListener(this); mViewPager.setAdapter(mAdapter); if (showPosition < 0 || showPosition >= mViews.size())

       showPosition = 0;
      

      if (isCycle) {

       showPosition = showPosition + 1;
      

      } mViewPager.setCurrentItem(showPosition); setWheel(true);//設置輪播 }

      /**

    • 獲取輪播圖View *
    • @param context
    • @param url */ private View getImageView(Context context, String url) { return MainActivity.getImageView(context, url); }

      /**

    • 設置指示器 *
    • @param selectedPosition 默認指示器位置 */ private void setIndicator(int selectedPosition) { setText(mTitle, infos.get(selectedPosition).getTitle()); try {

       for (int i = 0; i < mIndicators.length; i++) {
           mIndicators[i]
                   .setBackgroundResource(mIndicatorUnselected);
       }
       if (mIndicators.length > selectedPosition)
           mIndicators[selectedPosition]
                   .setBackgroundResource(mIndicatorSelected);
      

      } catch (Exception e) {

       Log.i(TAG, "指示器路徑不正確");
      

      } }

      /**

    • 頁面適配器 返回對應的view *
    • @author Yuedong Li */ private class ViewPagerAdapter extends PagerAdapter { @Override public int getCount() {

       return mViews.size();
      

      } @Override public boolean isViewFromObject(View arg0, Object arg1) {

       return arg0 == arg1;
      

      } @Override public void destroyItem(ViewGroup container, int position, Object object) {

       container.removeView((View) object);
      

      } @Override public View instantiateItem(ViewGroup container, final int position) {

       View v = mViews.get(position);
       if (mImageCycleViewListener != null) {
           v.setOnClickListener(new OnClickListener() {
               @Override
               public void onClick(View v) {
                mImageCycleViewListener.onImageClick(
                   infos.get(mCurrentPosition - 1), mCurrentPosition, v);
               }
           });
       }
       container.addView(v);
       return v;
      

      } @Override public int getItemPosition(Object object) {

       return POSITION_NONE;
      

      } } @Override public void onPageScrolled( int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int arg0) { int max = mViews.size() - 1; int position = arg0; mCurrentPosition = arg0; if (isCycle) {

       if (arg0 == 0) {
           //滾動到mView的1個(界面上的最后一個),將mCurrentPosition設置為max - 1
           mCurrentPosition = max - 1;
       } else if (arg0 == max) {
           //滾動到mView的最后一個(界面上的第一個),將mCurrentPosition設置為1
           mCurrentPosition = 1;
       }
       position = mCurrentPosition - 1;
      

      } setIndicator(position); } @Override public void onPageScrollStateChanged(int state) { if (state == 1) { // viewPager在滾動

       isScrolling = true;
       return;
      

      } else if (state == 0) { // viewPager滾動結束

       releaseTime = System.currentTimeMillis();
       //跳轉到第mCurrentPosition個頁面(沒有動畫效果,實際效果頁面上沒變化)
       mViewPager.setCurrentItem(mCurrentPosition, false);
      

      } isScrolling = false; }

      /**

    • 為textview設置文字
    • @param textView
    • @param text */ public static void setText(TextView textView, String text) { if (text != null && textView != null) textView.setText(text); }

      /**

    • 為textview設置文字 *
    • @param textView
    • @param text */ public static void setText(TextView textView, int text) { if (textView != null) setText(textView, text + ""); }

      /**

    • 是否循環,默認開啟。必須在setData前調用 *
    • @param isCycle 是否循環 */ public void setCycle(boolean isCycle) { this.isCycle = isCycle; }

      /**

    • 是否處于循環狀態 *
    • @return */ public boolean isCycle() { return isCycle; }

      /**

    • 設置是否輪播,默認輪播,輪播一定是循環的 *
    • @param isWheel */ public void setWheel(boolean isWheel) { this.isWheel = isWheel; isCycle = true; if (isWheel) {

       handler.postDelayed(runnable, delay);
      

      } }

      /**

    • 刷新數據,當外部視圖更新后,通知刷新數據 */ public void refreshData() { if (mAdapter != null)

       mAdapter.notifyDataSetChanged();
      

      }

      /**

    • 是否處于輪播狀態 *
    • @return */ public boolean isWheel() { return isWheel; }

      /**

    • 設置輪播暫停時間,單位毫秒(默認4000毫秒)
    • @param delay */ public void setDelay(int delay) { this.delay = delay; }

      /**

    • 輪播控件的監聽事件 *
    • @author minking */ public static interface ImageCycleViewListener {

      /**

      • 單擊圖片事件 *
      • @param info
      • @param position
      • @param imageView */ public void onImageClick(Info info, int position, View imageView); } }</code></pre>

        從里面挑了幾個變量和方法說明一下:

        變量:

        handler 、 runnable :實現定時輪播

        mCurrentPosition :表示當前位置

        方法:

        setIndicators() :設置指示器的圖片(必須在 setData 前調用)

        setData() :根據數據,生成對應的輪播圖

        setIndicator() :設置指示器和文字內容

        onPageSelected() 、 onPageScrollStateChanged() :利用 ViewPager 的滾動監聽,實現了上面的思路。 onPageSelected() 中根據 ViewPager 中顯示的位置,改變 mCurrentPosition 的值,然后在 onPageScrollStateChanged() 中根據 mCurrentPosition 重新設置頁面(這里的 setCurrentItem 沒有動畫效果)。

        getImageView() :根據URL生成 Viewpager 中對應的各個 View ( 根據實際的圖片加載框架來生成,這里使用了Picasso實現了網絡圖片的加載 ),看看 getImageView() 中調用的代碼

        /**
    • 得到輪播圖的View
    • @param context
    • @param url
    • @return */ public static View getImageView(Context context, String url) { RelativeLayout rl = new RelativeLayout(context); //添加一個ImageView,并加載圖片 ImageView imageView = new ImageView(context); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(

           RelativeLayout.LayoutParams.MATCH_PARENT,
           RelativeLayout.LayoutParams.MATCH_PARENT);
      

      imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setLayoutParams(layoutParams); //使用Picasso來加載圖片 Picasso.with(context).load(url).into(imageView); //在Imageview前添加一個半透明的黑色背景,防止文字和圖片混在一起 ImageView backGround = new ImageView(context); backGround.setLayoutParams(layoutParams); backGround.setBackgroundResource(R.color.cycle_image_bg); rl.addView(imageView); rl.addView(backGround); return rl; }</code></pre>

      <color name="cycle_image_bg">#44222222</color>

      代碼很簡單,創建了一個顯示圖片的布局,先在布局中添加了需要顯示的圖片,然后加了個半透明的圖,防止顯示時文字和圖片中白色的部分重疊在一起,導致看不清文字。

      </li>
    • 在Acitivty中使用

      輪子打造好了,不拿出來溜一溜?

      public class MainActivity extends AppCompatActivity {

      /**

    • 模擬請求后得到的數據 */ List<Info> mList = new ArrayList<>();

      /**

    • 輪播圖 */ CycleViewPager mCycleViewPager;

      @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); }

      /**

    • 初始化數據 */ private void initData() { mList.add(new Info("標題1",

         "http://img2.3lian.com/2014/c7/25/d/40.jpg"));
      

      mList.add(new Info("標題2",

         "http://img2.3lian.com/2014/c7/25/d/41.jpg"));
      

      mList.add(new Info("標題3",

        "http://imgsrc.baidu.com/forum/pic/item/b64543a98226cffc8872e00cb9014a90f603ea30.jpg"));
      

      mList.add(new Info("標題4",

        "http://imgsrc.baidu.com/forum/pic/item/261bee0a19d8bc3e6db92913828ba61eaad345d4.jpg"));
      

      }

      /**

    • 初始化View */ private void initView() { mCycleViewPager = (CycleViewPager) findViewById(R.id.cycle_view); //設置選中和未選中時的圖片 mCycleViewPager.setIndicators(R.mipmap.ad_select, R.mipmap.ad_unselect); //設置輪播間隔時間 mCycleViewPager.setDelay(2000); mCycleViewPager.setData(mList, mAdCycleViewListener); }

      /**

    • 輪播圖點擊監聽 */ private CycleViewPager.ImageCycleViewListener mAdCycleViewListener =

             new CycleViewPager.ImageCycleViewListener() {
      
      

      @Override public void onImageClick(Info info, int position, View imageView) {

       if (mCycleViewPager.isCycle()) {
           position = position - 1;
       }
       Toast.makeText(MainActivity.this, info.getTitle() +
            "選擇了--" + position, Toast.LENGTH_LONG).show();
      

      } }; }</code></pre> </li> </ul>

      使用起來也是很簡單的,只要設置下圖片、數據、點擊監聽就可以了。(之前貼過 MainActivity.getImageView() 方法了,這里就不貼了)

      放到自己的項目中?

      只需要調下布局,根據自己的圖片加載框架改下 getImageView (或者也可以直接用我的),然后把 CycleViewPager 中的 Info 改成自己的 Model 就可以了。

       

       

       

 本文由用戶 一杯倒 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!