android 实现自动滚动的 Banner 横幅

很多音乐播放器如qq音乐,kugou音乐等都有一个专辑推荐的那个横幅,它扩展了软件的空间,也为用户带来了更好的交互感受。

在此,我也模仿着实现了此效果,不足之处请大家见谅,欢迎提出问题,和大家一起学习。

我给他取名叫【BannerLayout】,主要是觉得它也如其他layout特性差不多吧。

public class BannerLayout extends ViewGroup {

public BannerLayout(Context context) {
        super(context);

        // TODO Auto-generated constructor stub
    }

    public BannerLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
    }

    public BannerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }
}
BannerLayout 继承与 ViewGroup. 
这个BannerLayout可以自动滚动,所以我们需要一个滚动器Scroller .
this.scroller = new Scroller(context, new DecelerateInterpolator(2));

我给scroller设置了一个插值器DecelerateInterpolator,就可以再滚动的时候实现滚动速度是中间快,开始和结束的时候慢的效果,个人觉得这个效果显得比较优雅。

要实现自动滚动,因此,我们可以使用handler还帮我们起到计时的作用。此外也可以使用timer等其他方式。

private Handler handler=new Handler()
    {
        @Override
        public void handleMessage(Message msg) {
       //autoScroll是一标志boolean亮,用来标志是否需要滚动,因此就能手动控制其滚动了,
if(autoScroll && currentWhat==msg.what) { currentScreenIndex=(currentScreenIndex+1)%getChildCount(); scrollToScreen(currentScreenIndex); Log.i("TAG","handleMessage scrollToScreen:"+currentScreenIndex); if(autoScroll)//给自身发送延时消息, handler.sendEmptyMessageDelayed(currentWhat, scrollTime); } } };

由于BannerLayout里面的子元素如图片都是水平布局的,所以我们需要手动控制它们在此布局的位置了,

重写onMeasure函数:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int maxHeight=-1;
        
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
            
            maxHeight=Math.max(maxHeight, getChildAt(i).getMeasuredHeight());
            
        }
// maxHeight
=Math.min(maxHeight, MeasureSpec.getSize(heightMeasureSpec)); Log.e("TAG","onMeasure Height:"+maxHeight); setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),maxHeight); }
@Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {

        final int count = getChildCount();

        int cLeft = 0;
        
        
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == View.GONE)
                continue;
            
//            child.setVisibility(View.VISIBLE);
            final int childWidth = child.getMeasuredWidth();
            child.layout(cLeft, 0, cLeft +childWidth, child.getMeasuredHeight());

            cLeft += childWidth;
        }
    }

此外,我们需要处理用户触摸事件,实现用户按下的时候停止滚动,用户拖动的时候能够随之移动,

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (getChildCount() == 0)
            return false;
        final int action = ev.getAction();
        final float x = ev.getX();
        switch (action) {
        case MotionEvent.ACTION_DOWN:

            autoScroll=false;
            
            currentWhat++;
            
            mLastMotionX = x;
            if (!scroller.isFinished()) {
                scroller.abortAnimation();
            }
            
//            Log.i("TAG","ACTION_DOWN");
            
            return true;

        case MotionEvent.ACTION_MOVE:
            final int deltaX = (int) (mLastMotionX - x);
//            boolean xMoved = Math.abs(deltaX) > mTouchSlop;
            mLastMotionX = x;
            
            if((0==currentScreenIndex && deltaX<0) || (getChildCount()-1==currentScreenIndex && deltaX>0))
                scrollBy(deltaX/4, 0);//此处实现了越界时候的阻尼效果
            else
//            Log.i("TAG","ACTION_MOVE");
//            if (xMoved)
                scrollBy(deltaX, 0);
            
            
            final int screenWidth = getWidth();
            currentScreenIndex=(getScrollX() + (screenWidth / 2))/ screenWidth;
            
            return true;
        case MotionEvent.ACTION_UP:
            snapToDestination();
            
            if(!autoScroll)
            {
                autoScroll=true;
                handler.sendEmptyMessageDelayed(currentWhat, scrollTime);
            }
            break;
        case MotionEvent.ACTION_CANCEL:
            snapToDestination();
            if(!autoScroll)
            {
                autoScroll=true;
                handler.sendEmptyMessageDelayed(currentWhat, scrollTime);
            }
            
        }
        return false;
    }

接下来,我们需要实现自定滚动到某一屏的效果:

private void scrollToScreen(int whichScreen)
    {
//        if (!scroller.isFinished())
//            return;
//        Log.e("TAG","scrollToScreen:"+whichScreen);
        int delta = 0;
        
        delta = whichScreen * getWidth() - getScrollX();
        
//        scroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
        scroller.startScroll(getScrollX(), 0, delta, 0, 1500);
        invalidate();
        
        currentScreenIndex=whichScreen;
    }
    private void snapToDestination()
    {
        final int x=getScrollX();
        final int screenWidth = getWidth();
        
        scrollToScreen((x + (screenWidth / 2))/ screenWidth);
    }

差不多就这些了,其实挺简单的,哈哈

下面是全部代码:

public class BannerLayout extends ViewGroup {

    private Scroller scroller;
    private float mLastMotionX;
//    private int mTouchSlop;
    private int currentScreenIndex=0;
    
    private boolean autoScroll=true;
    
    private int scrollTime=3*1000;
    
    private int currentWhat=0;
    
    private Handler handler=new Handler()
    {
        @Override
        public void handleMessage(Message msg) {
            if(autoScroll && currentWhat==msg.what)
            {
                currentScreenIndex=(currentScreenIndex+1)%getChildCount();
                scrollToScreen(currentScreenIndex);
                
                Log.i("TAG","handleMessage scrollToScreen:"+currentScreenIndex);
                
                if(autoScroll)
                    handler.sendEmptyMessageDelayed(currentWhat, scrollTime);
            }
        }
    };
    
    public BannerLayout(Context context) {
        super(context);

        initView(context);
        // TODO Auto-generated constructor stub
    }

    public BannerLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
        initView(context);
    }

    public BannerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
        // TODO Auto-generated constructor stub
    }

    private void initView(final Context context) {
        this.scroller = new Scroller(context, new DecelerateInterpolator(2));//OvershootInterpolator(1.1f)

        
        handler.sendEmptyMessageDelayed(currentWhat, scrollTime);
        
//        final ViewConfiguration configuration = ViewConfiguration
//                .get(getContext());
//        mTouchSlop = configuration.getScaledTouchSlop();
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int maxHeight=-1;
        
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
            
            maxHeight=Math.max(maxHeight, getChildAt(i).getMeasuredHeight());
            
        }
        maxHeight=Math.min(maxHeight, MeasureSpec.getSize(heightMeasureSpec));
        
        Log.e("TAG","onMeasure Height:"+maxHeight);
        
        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),maxHeight);
    }
    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {

        final int count = getChildCount();

        int cLeft = 0;
        
        
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == View.GONE)
                continue;
            
//            child.setVisibility(View.VISIBLE);
            final int childWidth = child.getMeasuredWidth();
            child.layout(cLeft, 0, cLeft +childWidth, child.getMeasuredHeight());

            cLeft += childWidth;
        }
    }

    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), 0);
            postInvalidate();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (getChildCount() == 0)
            return false;
        final int action = ev.getAction();
        final float x = ev.getX();
        switch (action) {
        case MotionEvent.ACTION_DOWN:

            autoScroll=false;
            
            currentWhat++;
            
            mLastMotionX = x;
            if (!scroller.isFinished()) {
                scroller.abortAnimation();
            }
            
//            Log.i("TAG","ACTION_DOWN");
            
            return true;

        case MotionEvent.ACTION_MOVE:
            final int deltaX = (int) (mLastMotionX - x);
//            boolean xMoved = Math.abs(deltaX) > mTouchSlop;
            mLastMotionX = x;
            
            if((0==currentScreenIndex && deltaX<0) || (getChildCount()-1==currentScreenIndex && deltaX>0))
                scrollBy(deltaX/4, 0);
            else
//            Log.i("TAG","ACTION_MOVE");
//            if (xMoved)
                scrollBy(deltaX, 0);
            
            
            final int screenWidth = getWidth();
            currentScreenIndex=(getScrollX() + (screenWidth / 2))/ screenWidth;
            
            return true;
        case MotionEvent.ACTION_UP:
            snapToDestination();
            
            if(!autoScroll)
            {
                autoScroll=true;
                handler.sendEmptyMessageDelayed(currentWhat, scrollTime);
            }
            break;
        case MotionEvent.ACTION_CANCEL:
            snapToDestination();
            if(!autoScroll)
            {
                autoScroll=true;
                handler.sendEmptyMessageDelayed(currentWhat, scrollTime);
            }
            
        }
        return false;
    }
    private void scrollToScreen(int whichScreen)
    {
//        if (!scroller.isFinished())
//            return;
//        Log.e("TAG","scrollToScreen:"+whichScreen);
        int delta = 0;
        
        delta = whichScreen * getWidth() - getScrollX();
        
//        scroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
        scroller.startScroll(getScrollX(), 0, delta, 0, 1500);
        invalidate();
        
        currentScreenIndex=whichScreen;
    }
    private void snapToDestination()
    {
        final int x=getScrollX();
        final int screenWidth = getWidth();
        
        scrollToScreen((x + (screenWidth / 2))/ screenWidth);
    }

    @Override
    protected void finalize() throws Throwable {

        Log.e("TAG","finalize===");

        super.finalize();
    }
    
}

 ok,附图一张

附上源码demo:https://files.cnblogs.com/zhouchanwen/bannerDemo.7z

原文地址:https://www.cnblogs.com/zhouchanwen/p/2757704.html