Android 自定義一個輪播圖

王世暉發表於2016-05-03

有限空間內展示更多的內容,輪播圖是個不錯的選擇,本文將實現一個輪播圖SlideShowView,效果如下圖所示:


因為輪播圖的每一個頁面都有文字和圖片,為了整合圖片和文字,SlideShowView選擇繼承Fraement

public class SlideShowView extends FrameLayout
輪播圖的主要屬性如下:

/*儲存圖片連結*/
private String[] imageUrls;
/*儲存文字內容*/
private String[] contents;
/*ViewPager裝圖片和文字*/
private ViewPager viewPager;
/*右下邊起到指示作用的小圓點*/
private List<View> dotViewsList;
/*定時功能,自動輪播*/
private ScheduledExecutorService scheduledExecutorService;
/*記錄輪播下標*/
private int currentItem  = 0;

輪播圖要求定時更換展示的內容,定時功能使用ScheduledExecutorService實現

/*開啟輪播*/
public void startPlay(){
    scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    scheduledExecutorService.scheduleAtFixedRate(new SlideShowTask(), 1, 5, TimeUnit.SECONDS);
}
定時時間到的話將會執行SlideShowTask任務,該任務主要是切換頁面,這裡更換下標即可,子執行緒不能更新UI,所以使用handler將訊息發給主執行緒

private class SlideShowTask implements Runnable {
    @Override
    public void run() {
        synchronized (viewPager) {
            currentItem = (currentItem+1)%imageUrls.length;
            handler.obtainMessage().sendToTarget();
        }
    }
}
handler在主執行緒中接受換頁訊息,執行換頁操作。為了避免handler記憶體洩露,將內部handler定義為靜態的,保持一個外部類的弱引用

private Handler handler = new SafeHandler(this);
static class SafeHandler extends Handler {
    WeakReference<SlideShowView> mOuter;
    SafeHandler(SlideShowView v) {
        mOuter= new WeakReference<SlideShowView>(v);
    }

    @Override
    public void handleMessage(Message msg) {
        final SlideShowView v = mOuter.get();
        super.handleMessage(msg);
        if (v != null) {
            v.viewPager.setCurrentItem(v.currentItem);
        }
    }
}
由於開啟了執行緒池和handler,為了防止生命週期問題導致的記憶體洩露,在輪播圖離開檢視被銷燬的時候要關閉執行緒池並且清除handler訊息回撥

/*關閉輪播*/
public void stopPlay() {
    if(scheduledExecutorService!=null){
        scheduledExecutorService.shutdown();
        scheduledExecutorService=null;
    }
}
@Override
public void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    stopPlay();
    handler.removeCallbacksAndMessages(null);
}
為了載入圖片,使用Picasso框架,根據url載入圖片並顯示在控制元件上

Picasso.with(context)
        .load(imageUrls[position])
        .placeholder(R.mipmap.slide_show_view_loading)
        .error(R.mipmap.slide_show_view_loading)
        .config(Bitmap.Config.RGB_565)
        /*.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)*/
        .into(image);
右下角小紅點指示器是一個LinearLayout,根據頁面數量動態生成

/*小紅點指示標誌處理*/
for (int i = 0; i < imageUrls.length; i++) {
    ImageView dotView =  new ImageView(context);
    dotView.setId(i);
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    params.leftMargin = 4;
    params.rightMargin = 4;
    dotLayout.addView(dotView, params);
    dotViewsList.add(dotView);
}
ImageView[] dotView = new ImageView[imageUrls.length];
for(int i = 0;i<imageUrls.length;i++){
      dotView[i]= (ImageView) findViewById(i);
      dotView[i].setOnClickListener(new OnClickListener() {
          @Override
          public void onClick(View v) {
              viewPager.setCurrentItem(v.getId());
          }
      });
}
ViewPager監聽介面滑動,自己實現一個 ViewPager.OnPageChangeListener即可


完整程式碼如下:

public class SlideShowView extends FrameLayout{
    private static final String TAG = "SlideShowView";
    private Context context;
    /*儲存圖片連結*/
    private String[] imageUrls;
    /*儲存文字內容*/
    private String[] contents;
    /*ViewPager裝圖片和文字*/
    private ViewPager viewPager;
    /*右下邊起到指示作用的小圓點*/
    private List<View> dotViewsList;
    /*定時功能,自動輪播*/
    private ScheduledExecutorService scheduledExecutorService;
    /*記錄輪播下標*/
    private int currentItem  = 0;

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

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

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

   /*傳入圖片連結和文字內容*/
    public void initData(String[] imageUrls,String[] contents) {
        this.imageUrls = imageUrls;
        this.contents = contents;
        dotViewsList = new ArrayList<>();
        initUI(context);
    }

    /*開啟輪播*/
    public void startPlay(){
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleAtFixedRate(new SlideShowTask(), 1, 5, TimeUnit.SECONDS);
    }

    /**
     * 發現使用這個輪播圖的地方沒有關閉執行緒池,導致記憶體嚴重洩露
     * by:wangshihui
     * at:2016/4/25 0025  10:29
     */
    /*關閉輪播*/
    public void stopPlay() {
        if(scheduledExecutorService!=null){
            scheduledExecutorService.shutdown();
            scheduledExecutorService=null;
        }
    }
    /*檢視銷燬釋放資源
    * This is called when the view is detached from a window.*/
    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stopPlay();
        handler.removeCallbacksAndMessages(null);
    }

    /**
     * Handler的正確用法
     * by:wangshihui
     * at:2016/4/28 0028  10:47
     */
    /*使用Handler注意記憶體洩露的問題*/
    private Handler handler = new SafeHandler(this);
    static class SafeHandler extends Handler {
        WeakReference<SlideShowView> mOuter;
        SafeHandler(SlideShowView v) {
            mOuter= new WeakReference<SlideShowView>(v);
        }

        @Override
        public void handleMessage(Message msg) {
            final SlideShowView v = mOuter.get();
            super.handleMessage(msg);
            if (v != null) {
                v.viewPager.setCurrentItem(v.currentItem);
            }
        }
    }

    /**
     *執行輪播圖切換任務
     */
    private class SlideShowTask implements Runnable {
        @Override
        public void run() {
            synchronized (viewPager) {
                currentItem = (currentItem+1)%imageUrls.length;
                handler.obtainMessage().sendToTarget();
            }
        }
    }

    /**
     * ViewPager的監聽器
     * 當ViewPager中頁面的狀態發生改變時呼叫
     */
    private class ViewPagerPageChangeListener implements ViewPager.OnPageChangeListener {
        boolean isPlaying = false;
        @Override
        public void onPageScrollStateChanged(int state) {
            switch (state) {
                /*正在滑動或拖動*/
                case 1:
                    isPlaying = false;
                    break;
                /*滑動結束*/
                case 2:
                    isPlaying = true;
                    break;
                /*什麼都沒做*/
                case 0:
                    /*滑動到頭尾後迴圈滑動處理*/
                    if (viewPager.getCurrentItem() == viewPager.getAdapter().getCount() - 1 && !isPlaying) {
                        viewPager.setCurrentItem(0);
                    }
                    else if (viewPager.getCurrentItem() == 0 && !isPlaying) {
                        viewPager.setCurrentItem(viewPager.getAdapter().getCount() - 1);
                    }
                    break;
            }
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {

        }

        @Override
        public void onPageSelected(int pos) {
            currentItem = pos;
            /*設定下邊的小圓點指示標誌*/
            for(int i=0;i < dotViewsList.size();i++){
                if(i == pos){
                    dotViewsList.get(pos).setBackgroundResource(R.mipmap.dot_focus);
                }else {
                    dotViewsList.get(i).setBackgroundResource(R.mipmap.dot_blur);
                }
            }
        }
    }

    private void initUI(final Context context) {
        View view = LayoutInflater.from(context).inflate(R.layout.viewpager_with_net, this,true);
        LinearLayout dotLayout = (LinearLayout)findViewById(R.id.dotLayout);
        /*手動播放時上一頁下一頁按鈕*/
        ImageView front = (ImageView) view.findViewById(R.id.back);
        ImageView next = (ImageView) view.findViewById(R.id.more);
        front.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if(currentItem>0){
                    currentItem = (currentItem-1)%imageUrls.length;
                    handler.obtainMessage().sendToTarget();
                }else{
                    currentItem =imageUrls.length-1;
                    handler.obtainMessage().sendToTarget();
                }
            }
        });
        next.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if(currentItem<imageUrls.length){
                    currentItem = (currentItem+1)%imageUrls.length;
                    handler.obtainMessage().sendToTarget();
                }
            }
        });
        /*小紅點指示標誌處理*/
        for (int i = 0; i < imageUrls.length; i++) {
            ImageView dotView =  new ImageView(context);
            dotView.setId(i);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
            params.leftMargin = 4;
            params.rightMargin = 4;
            dotLayout.addView(dotView, params);
            dotViewsList.add(dotView);
        }
        ImageView[] dotView = new ImageView[imageUrls.length];
        for(int i = 0;i<imageUrls.length;i++){
              dotView[i]= (ImageView) findViewById(i);
              dotView[i].setOnClickListener(new OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      viewPager.setCurrentItem(v.getId());
                  }
              });
        }
        viewPager = (ViewPager) view.findViewById(R.id.view);
        viewPager.setAdapter(new ViewPagerAdapter());
        viewPager.setOnPageChangeListener(new ViewPagerPageChangeListener());
    }

    private class ViewPagerAdapter extends PagerAdapter {
        @Override
        public int getCount() {
            return imageUrls.length;
        }
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view ==object;
        }
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            View view = LayoutInflater.from(context).inflate(R.layout.viewpager_item, null);
            TextView content = (TextView) view.findViewById(R.id.content);
            ImageView image = (ImageView) view.findViewById(R.id.imageview);
            content.setText(contents[position]);
            Logger.d(imageUrls[position]);
            /*檢視大圖放棄memory cache
            在檢視大圖時放棄使用記憶體快取,圖片從網路下載完成後會快取到磁碟中
            載入會從磁碟中載入,這樣可以加速記憶體的回收。
            其中memoryPolicy的NO_CACHE是指圖片載入時放棄在記憶體快取中查詢,
            NO_STORE是指圖片載入完不快取在記憶體中。*/
            Picasso.with(context)
                    .load(imageUrls[position])
                    .placeholder(R.mipmap.slide_show_view_loading)
                    .error(R.mipmap.slide_show_view_loading)
                    .config(Bitmap.Config.RGB_565)
                    /*.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)*/
                    .into(image);
            container.addView(view);
            return view;
        }
        @Override
        public void destroyItem(View container, int position, Object object) {
            ((ViewPager) container).removeView((View) object);
        }
    }
}

佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.ViewPager
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/back"
        android:layout_gravity="left"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:layout_alignParentLeft="true"
        android:paddingLeft="5dp"
        android:paddingRight="5dp"
        android:src="@mipmap/backa"/>
    <ImageView
        android:layout_gravity="right"
        android:id="@+id/more"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:layout_alignParentRight="true"
        android:paddingRight="5dp"
        android:paddingLeft="5dp"
        android:src="@mipmap/nexta"/>

    <LinearLayout android:id="@+id/dotLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:layout_gravity="bottom|right"
        android:gravity="right"
        android:orientation="horizontal">
    </LinearLayout>
</FrameLayout>




相關文章