天天看點

android自定義輪播圖廣告

前段時間自己自定義了一個輪播圖,使用起來很簡單,分享給大家使用,如有什麼缺陷或是問題,請留言提出建議。

我自定義的類叫做BannerView.java,整個輪播圖的實作都在這個類裡面,在這個類裡面我向外提供了很多方法,可以根據項目情況或是個人喜歡定義,比如有一個設定訓示器位置的方法(左中右,預設是居中),一個設定訓示器樣式的方法,預設是引用我自己畫的圓點,想要其他樣式可以自己去定義,還有一個就是播放的時間方法,BannerView裡面注釋都很清楚。

先看看效果圖吧,因為圖檔顔色原因在這裡訓示器看不是很清楚

android自定義輪播圖廣告

這裡分别使用 java代碼 跟 kotlin代碼 兩種形式來實作功能

在看看使用的方法

一。在布局檔案中添加自定義的輪播圖控件

android自定義輪播圖廣告

二。添加圖檔資源,這裡添加了三張圖檔

android自定義輪播圖廣告

三。最後一步,讓輪播圖輪播起來,那就是開啟定時器,關閉頁面是關閉定時器,在生命周期中調用

android自定義輪播圖廣告

自此,輪播圖功能就實作了,是不是很簡單,好了接下來就是BannerView.java這個最重要的類了,簡單說一下某些主要的實作功能,下文後面會給出完整代碼

既然是自定義View,不關聯任何布局xml檔案,那麼首先當然是動态建立最外層布局,我用了一個FrameLayout,裡面包含一個ViewPager,在最下面是一個LinearLayout,用來存放訓示器的,實作代碼如下:

android自定義輪播圖廣告

接下來是訓示器的實作,也是動态建立的,同時還要設定訓示器的位置,動态建立的過程代碼量比較多,就不貼出。

這個自定義輪播圖的原理是使用ViewPager來實作的,是以肯定需要一個ViewPager的擴充卡,裡面最主要的方法是根據外面setData()時傳入的List<String> 的size來動态建立ImageView,然後使用Glide來加載圖檔,代碼如下:

android自定義輪播圖廣告

還有一點就是輪播圖是可以手動滑動的,是以有一個需要注意的問題就是,當你手動滑動的時候,輪播圖的自動播放功能要失效,當你不手動去滑動的時候,輪播圖又可以自動去輪播,是以需要一個boolean去判斷目前是否是出于手動滑動狀态,若是,定時器就不向handler發送資訊,不做切換圖檔的操作,當沒有手動滑動,定時器就像handler發送資訊,實作圖檔切換,代碼如下:

android自定義輪播圖廣告
android自定義輪播圖廣告
android自定義輪播圖廣告

好了就講這麼多了,其實挺簡單的,若覺得不好,請給出建議,大家一起學習,若覺得好,就關注分享給身邊需要的朋友吧

BannerView.java代碼:

package qin.zszc.com.basiclib.widget.custom;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.bumptech.glide.Glide;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import qin.zszc.com.basiclib.R;

/**
 * Created by xcfor on 2018/7/11.
 */

public class BannerView extends FrameLayout implements ViewPager.OnPageChangeListener {

    public final int INDICATOR_LOCATION_CENTER_BUTTON = 0x1;//訓示器居中
    public final int INDICATOR_LOCATION_LEFT_BUTTON = 0x2;//訓示器居左
    public final int INDICATOR_LOCATION_RIGHT_BUTTON = 0x3;//訓示器居右
    public final int BANNER_DELAY_TIME = 1000; //輪播圖延遲時間
    public final int BANNER_DEF_TIIME = 5000;//輪播圖預設切換時間

    /**
     * 存放圓點訓示器
     */
    private LinearLayout mIndecatorLayout;
    private PagerAdapter mAdapter;
    private List<String> mUrls; //圖檔路徑list
    private Context mContext;
    private ViewPager mViewPager;


    private boolean mIsScrollingByUser;// 使用者手動滑動廣告中
    private int mIndicatorDotWidth;//輪播切換小圓點寬度預設寬度
    private int mCurrentPos = 0;//目前頁pos
    private int mPrePos = 0; //曆史頁pos
    private int mCount;//輪播圖總數
    private int mIndicatorLocation = 1;//預設值為1:居中  2:左  3:右
    private boolean mChangeIndecatorStyle = false;//是否自定義訓示器的樣式
    private float mScale;//顯示度量的密度

    /**
     * 定時滾動定時器
     */
    private Timer mTimer;
    private TimerTask mTask;


    /**
     * 接收定時器資訊的handler
     */
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            int what = msg.what;
            switch (what) {
                case 0:
                    if (mViewPager != null) {
                        if (!mIsScrollingByUser) {
                            if (mCurrentPos == mUrls.size()) {
                                mCurrentPos = 0;
                            } else {
                                mCurrentPos++;
                            }
                        }
                        mViewPager.setCurrentItem(mCurrentPos);
                    }
                    break;
            }
        }
    };

    public BannerView(@NonNull Context context) {
        super(context);
        this.mContext = context;
    }

    public BannerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
    }

    public BannerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    public void init() {

        if (mUrls.size() != 1) {
            mViewPager.addOnPageChangeListener(this);
        }
        //切換畫面
        mViewPager.setPageTransformer(true, new MyPageTransformer());
        mIndecatorLayout.removeAllViews();
        //向線性布局中添加小圓點訓示器
        if (mUrls.size()>1){
            View dot;
            LinearLayout.LayoutParams params;
            WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
            DisplayMetrics displayMetrics = new DisplayMetrics();
            windowManager.getDefaultDisplay().getMetrics(displayMetrics);
            int width = displayMetrics.widthPixels;
            for (int i = 0; i < mCount; i++) {
                dot = new View(mContext);
                params = new LinearLayout.LayoutParams(mIndicatorDotWidth, mIndicatorDotWidth);
                params.setMargins(mIndicatorDotWidth, 0, 0, dip2px(10));
                dot.setLayoutParams(params);
                dot.setBackgroundResource(R.drawable.basiclib_dot_bg_selector);
                dot.setEnabled(false);//預設設為非選中
                mIndecatorLayout.addView(dot);
            }
            //訓示器圖示的位置
            switch (mIndicatorLocation){
                case INDICATOR_LOCATION_CENTER_BUTTON:
                    if (mCount % 2 == 0) {
                        mIndecatorLayout.setPadding(width / 2 - (mCount * mIndicatorDotWidth), 0, 0, 8);
                    } else {
                        mIndecatorLayout.setPadding(width / 2 - (mCount * mIndicatorDotWidth), 0, 0, 8);
                    }
                    break;
                case INDICATOR_LOCATION_LEFT_BUTTON:
                    mIndecatorLayout.setPadding(0, 0, 0, 8);
                    break;
                case INDICATOR_LOCATION_RIGHT_BUTTON:
                    mIndecatorLayout.setPadding(width - ((mCount * 2+1) * mIndicatorDotWidth), 0, 0, 8);
                    break;
            }
            int midPos = Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % mCount;
            mCurrentPos = midPos;
            mIndecatorLayout.getChildAt(0).setEnabled(true);
            mViewPager.setCurrentItem(midPos);
        }else {
            mIndecatorLayout.setVisibility(GONE);
        }

    }

    /**
     * 設定訓示器的位置
     *
     * @param location
     */
    public void relayoutIndicator(int location) {
        mIndicatorLocation = location;
    }


    /**
     * 自定義訓示器入口
     *
     * @param style
     */
    public void customerIndicatorEntry(int style) {
        if (!mChangeIndecatorStyle) {
            return;
        } else {
            changeIndicator(style);
        }
    }

    /**
     * 改變訓示器
     * @param style
     */
    public void changeIndicator(int style) {
        for (int i = 0; i < mCount; i++) {
            mIndecatorLayout.getChildAt(i).setBackgroundResource(style);
        }
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //動态建立輪播圖最外圍布局
        mIndicatorDotWidth = dip2px(4);
        FrameLayout fl = new FrameLayout(mContext);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 
        	ViewGroup.LayoutParams.MATCH_PARENT);
        mViewPager = new ViewPager(mContext);
        mViewPager.setLayoutParams(params);
        fl.addView(mViewPager);

        //動态建立訓示器外圍布局
        mIndecatorLayout = new LinearLayout(mContext);
        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        params.gravity = Gravity.BOTTOM;
        mIndecatorLayout.setLayoutParams(params);
        mIndecatorLayout.setOrientation(LinearLayout.HORIZONTAL);
        fl.addView(mIndecatorLayout);
        addView(fl);
    }

    /**
     * 擷取資料
     */
    public void setData(List<String> urls) {
        if (urls.isEmpty()) {
            return;
        }
        this.mUrls = urls;
        mCount = mUrls.size();
        if (mAdapter == null) {
            mAdapter = new MyAdapter(mUrls);
            mViewPager.setAdapter(mAdapter);
        }else{
            //更新viewpager視圖
            //--更新原點訓示器
            mAdapter.notifyDataSetChanged();
        }
        init();
    }

    /**
     * 預設時間啟動定時器
     */
    public void startBannerScrollTask(){
        if (mUrls.size() != 1){
            startBannerScrollTask(BANNER_DEF_TIIME);
        }
    }

    /**
     * 給外邊調用,可以使用外邊的擴充卡
     * @param timer
     */
    public void setScrollTask(Timer timer ,long timeSpace){
        if (mCount == 0){
            return;
        }
        mTask = new TimerTask() {
            @Override
            public void run() {
                if (!mIsScrollingByUser){
                    mHandler.sendEmptyMessage(0);
                }

            }
        };
        timer.schedule(mTask,BANNER_DELAY_TIME, timeSpace);
    }


    //開啟輪播圖定時器
    public void startBannerScrollTask(long timeSpace) {
        if (mCount == 0) {
            return;
        }
        mTimer = new Timer(true);
        mTask = new TimerTask() {
            @Override
            public void run() {
                if (!mIsScrollingByUser){
                    mHandler.sendEmptyMessage(0);
                }

            }
        };
        mTimer.schedule(mTask, BANNER_DELAY_TIME, timeSpace);//1000ms後按指定時間間隔輪播
    }

    /**
     * 關閉輪播圖定時器
     */
    public void stopBannerTask() {
        if (mTask != null) {
            mTask.cancel();
        }
    }



    /**
     * 根據手機的分辨率從 dip 的機關 轉成為 px(像素)
     * 改全變量
     */
    public  int dip2px(float dpValue) {
        mScale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dpValue * mScale + 0.5f);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        mCurrentPos = position;
        mIndecatorLayout.getChildAt(mPrePos % mUrls.size()).setEnabled(false);
        mIndecatorLayout.getChildAt(mCurrentPos % mUrls.size()).setEnabled(true);//設定true放後面,防止初始化時兩個pos都為0時。沒有預設選中
        mPrePos = mCurrentPos;
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == 0) {//使用者手動滑動廣告時,取消自動翻頁響應
            mIsScrollingByUser = false;
        } else {
            //使用者手動滑動中
            mIsScrollingByUser = true;
        }
    }

    /**
     * 輪播圖ViewPager擴充卡
     */
    class MyAdapter extends PagerAdapter {
        List<String> addUrls;

        public MyAdapter(List<String> addUrls) {
            this.addUrls = addUrls;
        }

        @Override
        public int getCount() {
            if (addUrls.size() == 1){
                return addUrls.size();
            }else {
                return Integer.MAX_VALUE;
            }
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ImageView imageView = new ImageView(mContext);
            String currentUrl = addUrls.get(position % addUrls.size());
            imageView.setScaleType(ImageView.ScaleType.FIT_XY);
            Glide.with(mContext).load(currentUrl).into(imageView);
            container.addView(imageView);
            return imageView;
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

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

    }

    /**
     * 添加viewPager頁面切換效果
     */
    class MyPageTransformer implements ViewPager.PageTransformer {

        @Override
        public void transformPage(View page, float position) {
            final float normalizedposition = Math.abs(Math.abs(position) - 1);
            page.setAlpha(normalizedposition);
        }
    }




}
           

kotlin代碼:

package com.example.testproject.utils

import android.annotation.SuppressLint
import android.content.Context
import android.os.Handler
import android.os.Message
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout

import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import com.bumptech.glide.Glide
import com.example.testproject.R
import java.util.*

class BannerView : FrameLayout, ViewPager.OnPageChangeListener {

    private val INDICATOR_LOCATION_CENTER_BUTTON: Int = 0x1//訓示器居中
    private val INDICATOR_LOCATION_LEFT_BUTTON: Int = 0x2//訓示器居左
    private val INDICATOR_LOCATION_RIGHT_BUTTON: Int = 0x3//訓示器居右
    private val BANNER_DELAY_TIME: Long = 1000//輪播圖延遲時間
    private val BANNER_DEF_TIIME: Long = 5000//輪播圖預設切換時間

    private var mIndecatorLayout: LinearLayout? = null
    private var mAdapter: PagerAdapter? = null
    private var mUrls: MutableList<String>? = null
    private var mViewPager: ViewPager? = null
    private var mContext: Context? = null

    private var mIsScrellingByUser: Boolean = false
    private var mIndicatorDotWidth: Int = 0
    private var mCurrentPos: Int = 0
    private var mPrePos: Int = 0
    private var mCount: Int = 0
    private var mIndicatorLocation: Int = 1
    private var mChangeIndecatorStyle: Boolean = false
    private var mScale: Float = 0.0f

    private var mTimer: Timer? = null
    private var mTask: TimerTask? = null

    constructor(context: Context) : super(context) {
        this.mContext = context
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        this.mContext = context
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){
        this.mContext = context
    }

    private var mHandler: Handler = @SuppressLint("HandlerLeak")
    object : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            val what = msg.what
            if (what == 0) {
                if (!mIsScrellingByUser) {
                    mCurrentPos = if (mCurrentPos == mUrls?.size) 0
                    else mCurrentPos.plus(1)
                }
                mViewPager?.currentItem = mCurrentPos
            }
        }
    }

    private fun init() {
        if (mUrls?.size != 1)
            mViewPager?.addOnPageChangeListener(this)
        //切換畫面
        mViewPager?.setPageTransformer(true, MyPageTransformer())
        mIndecatorLayout?.removeAllViews()
        //向線性布局中添加小圓點訓示器
        if (mUrls?.size?.compareTo(1)!! > 0) {
            var dot: View
            var params: LinearLayout.LayoutParams
            val windowManager: WindowManager = mContext?.getSystemService(Context.WINDOW_SERVICE) as WindowManager
            val display = DisplayMetrics()
            windowManager.defaultDisplay.getMetrics(display)
            val width: Int = display.widthPixels
            for (item in 0 until mCount) {
                dot = View(mContext)
                params = LinearLayout.LayoutParams(mIndicatorDotWidth, mIndicatorDotWidth)
                params.setMargins(mIndicatorDotWidth, 0, 0, dip2px(10f))
                dot.layoutParams = params
                dot.setBackgroundResource(R.mipmap.ic_launcher)
                dot.isEnabled = false
                mIndecatorLayout?.addView(dot)
            }
            //訓示器圖示的位置
            when (mIndicatorLocation) {
                INDICATOR_LOCATION_CENTER_BUTTON ->
                    if (mCount % 2 == 0)
                        mIndecatorLayout?.setPadding(width / 2 - (mCount * mIndicatorDotWidth), 0, 0, 8)
                    else
                        mIndecatorLayout?.setPadding(width / 2 - (mCount * mIndicatorDotWidth), 0, 0, 8)
                INDICATOR_LOCATION_LEFT_BUTTON -> mIndecatorLayout?.setPadding(0, 0, 0, 8)
                INDICATOR_LOCATION_RIGHT_BUTTON -> mIndecatorLayout?.setPadding(width - ((mCount * 2 + 1) * mIndicatorDotWidth), 0, 0, 8)
            }
            val midPos: Int = Int.MAX_VALUE / 2 - Int.MAX_VALUE / 2 % mCount
            mCurrentPos = midPos
            mIndecatorLayout?.getChildAt(0)?.isEnabled = true
            mViewPager?.currentItem = midPos
        } else {
            mIndecatorLayout?.visibility = View.GONE
        }
    }

    /**
     * 設定訓示器的位置
     */
    fun relayoutIndicator(location: Int) {
        mIndicatorLocation = location
    }

    /**
     * 自定義訓示器入口
     */
    fun customerIndicatorEntry(style: Int) {
        if (!mChangeIndecatorStyle)
            return
        else
            changeIndicator(style)
    }

    /**
     * 改變訓示器
     */
    private fun changeIndicator(style: Int) {
        for (item in 0 until mCount) {
            mIndecatorLayout?.getChildAt(item)?.setBackgroundResource(style)
        }
    }

    private fun dip2px(dpValue: Float): Int {
        mScale = mContext?.resources?.displayMetrics?.density!!
        return (dpValue * mScale + 0.5f).toInt()
    }

    override fun onFinishInflate() {
        super.onFinishInflate()
        //動态建立輪播圖最外圍布局
        mIndicatorDotWidth = dip2px(4f)
        val fl = FrameLayout(mContext!!)
        val params = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
        mViewPager = ViewPager(mContext!!)
        mViewPager?.layoutParams = params
        fl.addView(mViewPager)
        //動态建立訓示器外圍布局
        mIndecatorLayout = LinearLayout(mContext!!)
        params.height = ViewGroup.LayoutParams.WRAP_CONTENT
        params.gravity = Gravity.BOTTOM
        mIndecatorLayout?.layoutParams = params
        mIndecatorLayout?.orientation = LinearLayout.HORIZONTAL
        fl.addView(mIndecatorLayout)
        addView(fl)
    }

    /**
     * 擷取資料
     */
    fun setData(urls: MutableList<String>) {
        if (urls.isEmpty()) return
        this.mUrls = urls
        mCount = mUrls!!.size
        if (mAdapter == null) {
            mAdapter = MyAdapter(mUrls!!, mContext!!)
            mViewPager?.adapter = mAdapter
        } else {
            mAdapter?.notifyDataSetChanged()
        }
        init()
    }

    /**
     * 啟動定時器(預設時間)
     */
    fun startBannerScrollTask() {
        if (mUrls?.size != 1)
            startBannerScrollTask(BANNER_DEF_TIIME)
    }

    /**
     * 給外部調用,可以使用外邊的擴充卡
     */
    fun setScrollTask(timer: Timer, timeSpace: Long) {
        if (mCount == 0)
            return
        mTask = object : TimerTask() {
            override fun run() {
                if (!mIsScrellingByUser)
                    mHandler.sendEmptyMessage(0)
            }
        }
        timer.schedule(mTask, BANNER_DELAY_TIME, timeSpace)
    }

    /**
     * 開啟輪播圖定時器
     */
    private fun startBannerScrollTask(timeSpace: Long) {
        if (mCount == 0)
            return
        mTimer = Timer(true)
        mTask = object : TimerTask() {
            override fun run() {
                if (!mIsScrellingByUser)
                    mHandler.sendEmptyMessage(0)
            }
        }
        mTimer?.schedule(mTask, BANNER_DELAY_TIME, timeSpace)
    }

    /**
     * 關閉輪播圖定時器
     */
    fun stopBannerTask() {
        if (mTask != null)
            mTask?.cancel()
    }

    override fun onPageScrollStateChanged(state: Int) {
        mIsScrellingByUser = state != 0
    }

    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
    }

    override fun onPageSelected(position: Int) {
        mCurrentPos = position
        mIndecatorLayout?.getChildAt(mPrePos % mUrls?.size!!)?.isEnabled = false
        mIndecatorLayout?.getChildAt(mCurrentPos % mUrls?.size!!)?.isEnabled = true
        mPrePos = mCurrentPos
    }

    class MyAdapter(addUrls: MutableList<String>, ct: Context) : PagerAdapter() {
        private var addUrls: MutableList<String>? = addUrls
        private var context: Context = ct

        override fun isViewFromObject(view: View, `object`: Any): Boolean {
            return view == `object`
        }

        override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
            container.removeView(`object` as View?)
        }

        override fun instantiateItem(container: ViewGroup, position: Int): Any {
            val img = ImageView(context)
            val currentUrl = addUrls?.get(position % addUrls?.size!!)
            img.scaleType = ImageView.ScaleType.FIT_XY
            Glide.with(context).load(currentUrl).into(img)
            container.addView(img)
            return img
        }

        override fun getCount(): Int {
            return if (addUrls?.size == 1)
                addUrls?.size!!
            else
                Int.MAX_VALUE
        }

    }

    class MyPageTransformer : ViewPager.PageTransformer {
        override fun transformPage(page: View, position: Float) {
            val normalizedPosition: Float = Math.abs(Math.abs(position)-1)
            page.alpha = normalizedPosition
        }
    }
}