BannerView Android 只一个方向

本文介绍了BannerView自定义ViewGroup实现的滑动广告组件,包含居中、顶部、底部布局模式,以及左右向滑动和动画效果。核心内容涉及布局计算、视图动画和用户交互控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

居中 LAYOUT_MODE_CENTER

左侧居中

顶部 LAYOUT_MODE_TOP

顶部

底部 LAYOUT_MODE_BOTTOM

底部

右往左布局 LAYOUT_RIGHT_TO_LEFT

在这里插入图片描述

BannerView
package com.example.bannerdemo;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;


public class BannerView extends ViewGroup implements Runnable {
    private final float SCALE_0 = 0.833333333333f;
    private final float SCALE_1 = 0.916666667f;
    private final float SCALE_2 = 1f;

    private final float ALPHA_0 = 0.4f;
    private final float ALPHA_1 = 0.8f;
    private final float ALPHA_2 = 1.0f;

    private final int DEFAULT_BASELINE = 30;
    // index=0 child baseline
    private int BASELINE_0 = 0;
    // index=1 child baseline
    private int BASELINE_1 = 0;
    // index=2 child baseline
    private int BASELINE_2 = 0;

    //动画间隔时间
    private int waitTime=1000;

    // 只能向左滑动
    private final int SCROLL_ORIENTATION_LEFT = 0;
    // 只能向右滑动
    private final int SCROLL_ORIENTATION_RIGHT = 1;
    // 双向滑动
    private final int SCROLL_ORIENTATION_ALL = 2;

    private final int LAYOUT_MODE_TOP = 0;
    private final int LAYOUT_MODE_CENTER = 1;
    private final int LAYOUT_MODE_BOTTOM = 2;

    private final int LAYOUT_LEFT_TO_RIGHT = 3;
    private final int LAYOUT_RIGHT_TO_LEFT = 4;


    // 布局模式
    int layoutMode = LAYOUT_MODE_CENTER;
    // 布局方向
    int layoutOrientation = LAYOUT_LEFT_TO_RIGHT;
    // 滑动方向
    int scrollOrientation = SCROLL_ORIENTATION_LEFT;
    // baseLine offset 子view偏移量
    int baseLineOffset = DEFAULT_BASELINE;
    // 最上面的左边距
    int topLeftBaseLine = 0;

    //item 点击事件
    private OnItemClickListener onItemClickListener;

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

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

    public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);

            int baseLeftLine = getBaseLeftLine(child);
            getBaseTopLine(i);

            layoutChild(child, baseLeftLine);
            if (i == 2) {
                child.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (onItemClickListener != null) {
                            LayoutParams lp = (LayoutParams) child.getLayoutParams();
                            onItemClickListener.onItemClick(lp.dataPosition);
                        }
                    }
                });
            } else {
                child.setOnClickListener(null);
            }
        }
    }

    private int getBaseLeftLine(View child) {
        LayoutParams lp = (LayoutParams) child.getLayoutParams();

        int baseLine = 0;
        switch (lp.fromIndex) {
            case 0:
                if (mOffsetPercent > 0) {
                    baseLine = (int) (BASELINE_0 - mOffsetPercent * BASELINE_1);
                    if (baseLine < BASELINE_1) {
                        baseLine = BASELINE_1;
                    }
                    lp.scale = (float) (SCALE_0 + ((SCALE_1 - SCALE_0) * mOffsetPercent));
                    if (lp.scale > SCALE_1) {
                        lp.scale = SCALE_1;
                    }
                } else {
                    baseLine = (int) (BASELINE_0 + mOffsetPercent * BASELINE_1);
                    if (baseLine < BASELINE_1) {
                        baseLine = BASELINE_1;
                    }
                    lp.scale = (float) (SCALE_0 - ((SCALE_1 - SCALE_0) * mOffsetPercent));
                    if (lp.scale > SCALE_1) {
                        lp.scale = SCALE_1;
                    }
                }

                break;
            case 1:
                // toIndex=2
                if (mOffsetPercent > 0) {
                    baseLine = (int) (BASELINE_1 - mOffsetPercent * BASELINE_1);
                    if (baseLine < BASELINE_2) {
                        baseLine = BASELINE_2;
                    }
                    lp.scale = (float) (SCALE_1 + ((SCALE_2 - SCALE_1) * mOffsetPercent));
                    if (lp.scale > SCALE_2) {
                        lp.scale = SCALE_2;
                    }
                    lp.alpha = (float) (ALPHA_1 + ((ALPHA_2 - ALPHA_1) * mOffsetPercent));
                    if (lp.alpha > ALPHA_2) {
                        lp.alpha = ALPHA_2;
                    }
                } else {
                    baseLine = (int) (BASELINE_1 + mOffsetPercent * BASELINE_1);
                    if (baseLine < BASELINE_2) {
                        baseLine = BASELINE_2;
                    }
                    lp.scale = (float) (SCALE_1 - ((SCALE_2 - SCALE_1) * mOffsetPercent));
                    if (lp.scale > SCALE_2) {
                        lp.scale = SCALE_2;
                    }
                    lp.alpha = (float) (ALPHA_1 - ((ALPHA_2 - ALPHA_1) * mOffsetPercent));
                    if (lp.alpha > ALPHA_2) {
                        lp.alpha = ALPHA_2;
                    }
                }

                break;
            case 2:
                // toIndex= 0
                if (mOffsetPercent > 0) {
                    baseLine = (int) (BASELINE_2 + mOffsetPercent * BASELINE_0);
                    if (baseLine >= BASELINE_0) {
                        baseLine = BASELINE_0;
                    } else {
                        baseLine = (int) (BASELINE_2 + mOffsetPercent * child.getWidth());
                    }
                    lp.scale = (float) (SCALE_2 - ((SCALE_2 - SCALE_0) * mOffsetPercent));
                    if (lp.scale <= SCALE_0) {
                        lp.scale = SCALE_0;
                    } else {
                        lp.scale = SCALE_2;
                    }
                    lp.alpha = (float) (ALPHA_2 - ((ALPHA_2 - ALPHA_0) * mOffsetPercent));
                    if (lp.alpha >= ALPHA_0) {
                        lp.alpha = ALPHA_0;
                    } else {
                        lp.alpha = ALPHA_2;
                    }
                } else {
                    baseLine = (int) (BASELINE_2 + mOffsetPercent * BASELINE_0);
                    if (baseLine >= BASELINE_0) {
                        baseLine = BASELINE_0;
                    } else {
                        baseLine = (int) (BASELINE_2 + mOffsetPercent * child.getWidth());
                    }
                    lp.scale = (float) (SCALE_2 + ((SCALE_2 - SCALE_0) * mOffsetPercent));
                    if (lp.scale <= SCALE_0) {
                        lp.scale = SCALE_0;
                    } else {
                        lp.scale = SCALE_2;
                    }
                    lp.alpha = (float) (ALPHA_2 + ((ALPHA_2 - ALPHA_0) * mOffsetPercent));
                    if (lp.alpha <= ALPHA_0) {
                        lp.alpha = ALPHA_0;
                    } else {
                        lp.alpha = ALPHA_2;
                    }
                }

                break;
        }
        return baseLine;
    }


    private int getBaseTopLine(int index) {
        int baseLine = 0;
        switch (index) {
            case 0:
                baseLine = 40;
                break;
            case 1:
                baseLine = 20;
                break;
            case 2:
                baseLine = 0;
                break;
        }
        return baseLine;
    }

    private void layoutChild(View child, int baseLeftLine) {
        LayoutParams lp = (LayoutParams) child.getLayoutParams();
        child.setAlpha(lp.alpha);
        int childWidth = child.getMeasuredWidth();
        int childHeight = (int) (child.getMeasuredHeight() * lp.scale);

        int top = 0;
        int bottom = 0;
        if (layoutMode == LAYOUT_MODE_CENTER) {
            top = (int) (getHeight() / 2.0f - childHeight / 2.0f);
            bottom = (int) (getHeight() / 2.0f + childHeight / 2.0f);

        } else if (layoutMode == LAYOUT_MODE_BOTTOM) {
            top = getHeight() - childHeight;
            bottom = getHeight();
        } else if (layoutMode == LAYOUT_MODE_TOP) {
            top = 0;
            bottom = childHeight;
        }
        int left = 0;
        int right = 0;
        if (layoutOrientation == LAYOUT_LEFT_TO_RIGHT) {
            left = baseLeftLine;
            right = left + childWidth;
        } else if (layoutOrientation == LAYOUT_RIGHT_TO_LEFT) {
            right = getWidth() - baseLeftLine;
            left = right - childWidth;
        }
        child.layout(left + lp.leftMargin, top + lp.topMargin, right + lp.rightMargin, bottom + lp.bottomMargin);

    }

    private int measureWidth(int widthMeasureSpec) {
        int width = 0;
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            width = size;
        } else {
            int maxChildWidth = 0;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
                maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin);
            }
            width = maxChildWidth + 60;
        }
        return width;
    }

    private int measureHeight(int heightMeasureSpec) {
        int height = 0;
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            height = size;
        } else {
            int maxChildHeight = 0;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
                maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin);
            }
            height = maxChildHeight;
        }
        return height;
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (index > 2) {
            return;
        }
        LayoutParams lp = params instanceof LayoutParams ? (LayoutParams) params : new LayoutParams(params);
        lp.fromIndex = index;
        switch (index) {
            case 0:
                lp.scale = SCALE_0;
                lp.alpha = ALPHA_0;
                BASELINE_0 = baseLineOffset * 2;
                break;
            case 1:
                lp.scale = SCALE_1;
                lp.alpha = ALPHA_1;
                BASELINE_1 = baseLineOffset;

                break;
            case 2:
                lp.scale = SCALE_2;
                lp.alpha = ALPHA_2;
                BASELINE_2 = baseLineOffset * 0;
                break;
        }
        Log.e("BannerView", "BASELINE_0  =" + BASELINE_0);
        Log.e("BannerView", "BASELINE_1  =" + BASELINE_1);
        Log.e("BannerView", "BASELINE_2  =" + BASELINE_2);
        super.addView(child, index, params);
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    public static class LayoutParams extends MarginLayoutParams {

        public int fromIndex;
        public int toIndex = -1;
        public int dataPosition;

        public float scale;
        public float alpha;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    float mLastX;
    boolean isBeingDragged;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        float x = ev.getX();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = x;
                isBeingDragged = true;
                break;
            case MotionEvent.ACTION_MOVE:
                float offsetX = x - mLastX;
                //判断是否触发拖动事件
                if (Math.abs(offsetX) > 60) {
                    //更新上一次的触摸坐标
                    mLastX = x;
                    //标记已开始拖拽

                }
                break;

            case MotionEvent.ACTION_OUTSIDE:
            case MotionEvent.ACTION_UP:
                isBeingDragged = false;
                break;
            case MotionEvent.ACTION_CANCEL:
                requestDisallowInterceptTouchEvent(true);
                break;

        }
        return isBeingDragged;
    }


    float mOffsetX;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (autoAnimator != null && autoAnimator.isRunning()) {
            return false;
        }

        float x = event.getX();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                removeCallbacks(this);
            case MotionEvent.ACTION_MOVE:
                float offsetX = x - mLastX;
                mOffsetX += offsetX;
                if (Math.abs(mOffsetX) > 60) {
                    isBeingDragged = true;
                }
                //百分比
                mOffsetPercent = mOffsetX / getWidth();
                // 开始移动
                onChildMove();
                break;
            case MotionEvent.ACTION_OUTSIDE:
            case MotionEvent.ACTION_UP:
                //开始动画,固定位子
                isBeingDragged = false;
                run();
                break;
        }
        mLastX = x;
        return true;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_OUTSIDE:
            case MotionEvent.ACTION_UP:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (autoAnimation) {
            run();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (autoAnimation) {
            removeCallbacks(this);
        }

        if (autoAnimator != null && autoAnimator.isRunning()) {
            autoAnimator.cancel();
        }
    }

    @Override
    public void run() {
        startAutoMove();
    }

    ValueAnimator autoAnimator;

    private boolean autoAnimation = true;

    public void setAutoAnimation(boolean autoAnimation) {
        this.autoAnimation = autoAnimation;
    }

    /**
     * 自动播放
     */
    public void startAutoMove() {
        if (getChildCount() != 3) {
            Log.e("BannerView", "child need 3 current count is" + getChildCount());
            return;
        }
        if (autoAnimator != null && autoAnimator.isRunning()) {
            return;
        }
        if (isBeingDragged) {
            return;
        }

        autoAnimator = ValueAnimator.ofFloat(mOffsetPercent, -1.f);
        autoAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mOffsetPercent = (float) animation.getAnimatedValue();
                onChildMove();
            }
        });
        autoAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isReordered = false;
                mOffsetX = 0;
                mOffsetPercent = 0;
                if (autoAnimation) {
                    postDelayed(BannerView.this, waitTime);
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

        autoAnimator.setDuration(500);
        autoAnimator.setInterpolator(mInterpolator);
        autoAnimator.start();

    }

    /**
     * 调整动画的插值器,现在是减速
     */
    private Interpolator mInterpolator = new DecelerateInterpolator();

    // 滑动的百分比
    float mOffsetPercent;

    private void onChildMove() {
        if (scrollOrientation == SCROLL_ORIENTATION_LEFT) {
            if (mOffsetPercent > 0) {
                return;
            }
        }
        if (scrollOrientation == SCROLL_ORIENTATION_RIGHT) {
            if (mOffsetPercent < 0) {
                return;
            }
        }
        changeChildFromAndTop();
        updateChildOrder();
        requestLayout();
    }

    private void changeChildFromAndTop() {
        if (Math.abs(mOffsetPercent) >= 1) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                LayoutParams lp = (LayoutParams) child.getLayoutParams();
                lp.fromIndex = lp.toIndex;
                lp.toIndex = -1;
            }
            isReordered = false;
            mOffsetPercent %= 1;
            mOffsetX %= getWidth();
        }

        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            switch (lp.fromIndex) {
                case 0:
                    lp.toIndex = mOffsetPercent > 0 ? 1 : 1;
                    break;
                case 1:
                    lp.toIndex = mOffsetPercent > 0 ? 2 : 2;
                    break;
                case 2:
                    lp.toIndex = mOffsetPercent > 0 ? 0 : 0;
                    break;
            }
        }
    }

    boolean isReordered;

    /**
     * 更新子View的层级顺序
     */
    private void updateChildOrder() {
        Log.e("bannerView", "updateChildOrder mOffsetPercent" + mOffsetPercent);
        if (Math.abs(mOffsetPercent) >= .9F) {
            if (!isReordered) {
                exchangeOrder(2, 0);
                exchangeOrder(2, 1);
                isReordered = true;
            }
        } else {
            if (isReordered) {
                exchangeOrder(0, 2);
                isReordered = false;

            }
        }
        for (int i=0;i<3;i++){
            View childAt = getChildAt(i);
            LayoutParams layoutParams = (LayoutParams) childAt.getLayoutParams();
            Log.e("bannerView", "updateChildOrder mOffsetPercent  i "+i +"from  "+ layoutParams.fromIndex);
            Log.e("bannerView", "updateChildOrder mOffsetPercent i "+i +"to  " +layoutParams.toIndex);
        }

    }


    /**
     * 交换子View的层级顺序
     *
     * @param fromIndex 原子View索引
     * @param toIndex   目标子View索引
     */

    private void exchangeOrder(int fromIndex, int toIndex) {
        Log.e("bannerView", "exchangeOrder fromIndex" + fromIndex);
        Log.e("bannerView", "exchangeOrder toIndex" + toIndex);
        if (fromIndex == toIndex || fromIndex >= getChildCount() || toIndex >= getChildCount()) {
            return;
        }

        if (fromIndex > toIndex) {
            int temp = fromIndex;
            fromIndex = toIndex;
            toIndex = temp;
        }

        View from = getChildAt(fromIndex);
        View to = getChildAt(toIndex);

        detachViewFromParent(toIndex);
        detachViewFromParent(fromIndex);

        attachViewToParent(to, fromIndex, to.getLayoutParams());
        attachViewToParent(from, toIndex, from.getLayoutParams());
        invalidate();
    }

    /**
     * 把目标子View放置到视图层级最底部
     */
    private void setAsBottom(View child) {
        int index = indexOfChild(child);
        if (index > 1) {
            return;
        }
        Log.e("bannerView", "setAsBottom index" + index);
        exchangeOrder(index, 0);
    }

    public interface OnItemClickListener {
        void onItemClick(int position);
    }
}

参考litepage

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值