总体效果如下:

\"\"

 

 

该程序总体分为二大部分,第一大部分主要是负责显示走马灯的效果;第二大部分主要是负责对走马灯的各种控制。比如自动旋转、以X为对称轴旋转,以Z为对称轴旋转等等。

实现走马灯的效果我们需要自定义一个继承RelativeLayout的布局,自定义布局:

public class CarrouselLayout  extends RelativeLayout{

    private Context mContext;
    //鑷姩鏃嬭浆 榛樿涓嶈嚜鍔�
    private boolean mAutoRotation;

    //鏃嬭浆闂撮殧鏃堕棿  榛樿璁剧疆涓�2绉�
    private int mRotationTime;

    //鏃嬭浆鏈ㄩ┈鏃嬭浆鍗婂緞  鍦嗙殑鍗婂緞
    private float mCarrouselR;

    //camera鍜屾棆杞湪椹窛绂�
    private float mDistance=2f*mCarrouselR;

    //鏃嬭浆鏂瑰悜 鍒�0椤烘椂閽堝拰 1閫嗘椂閽� 淇鏃嬭浆鏈ㄩ┈鐪�
    private int mRotateDirection;

    //handler
    private CarrouselRotateHandler mHandler;

    //鎵嬪娍澶勭悊
    private GestureDetector mGestureDetector;

    //x鏃嬭浆
    private int mRotationX;

    //Z鏃嬭浆
    private int mRotationZ;

    //鏃嬭浆鐨勮搴�
    private float mAngle = 0;

    //鏃嬭浆鏈ㄩ┈瀛恦iew
    private List<View> mCarrouselViews=new ArrayList<>();

    //鏃嬭浆鏈ㄩ┈瀛恦iew鐨勬暟閲�
    private int  viewCount;

    //鍗婂緞鎵╂暎鍔ㄧ敾
    private ValueAnimator mAnimationR;

    //璁板綍鏈�鍚庣殑瑙掑害 鐢ㄦ潵璁板綍涓婁竴娆″彇娑坱ouch涔嬪悗鐨勮搴�
    private float mLastAngle;

    //鏄惁鍦ㄨЕ鎽�
    private boolean isTouching;

    //鏃嬭浆鍔ㄧ敾
    private ValueAnimator restAnimator;

    //閫変腑item
    private int selectItem;

    //item閫変腑鍥炶皟鎺ュ彛
    private OnCarrouselItemSelectedListener mOnCarrouselItemSelectedListener;

    //item鐐瑰嚮鍥炶皟鎺ュ彛
    private OnCarrouselItemClickListener mOnCarrouselItemClickListener;

    //x杞存棆杞姩鐢�
    private ValueAnimator xAnimation;

    //z杞存棆杞姩鐢�
    private ValueAnimator zAnimation;

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

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

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

    private void init(Context context, AttributeSet attrs) {
        this.mContext=context;
        TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.CarrouselLayout);
        mAutoRotation=typedArray.getBoolean(R.styleable.CarrouselLayout_autoRotation,false);
        mRotationTime=typedArray.getInt(R.styleable.CarrouselLayout_rotationTime,2000);
        mCarrouselR=typedArray.getDimension(R.styleable.CarrouselLayout_r,200);
        mRotateDirection=typedArray.getInt(R.styleable.CarrouselLayout_rotateDirection,0);
        typedArray.recycle();
        mGestureDetector = new GestureDetector(context, getGestureDetectorController());
        initHandler();
    }

    /**
     * 鍒濆鍖杊andler瀵硅薄
     */
    private void initHandler() {
        mHandler=new CarrouselRotateHandler(mAutoRotation,mRotationTime,mRotateDirection) {
            @Override
            public void onRotating(CarrouselRotateDirection rotateDirection) {//鎺ュ彈鍒伴渶瑕佹棆杞寚浠�
                try {
                    if (viewCount != 0) {//鍒ゆ柇鑷姩婊戝姩浠庨偅杈瑰紑濮�
                        int perAngle = 0;
                        switch (rotateDirection){
                            case clockwise:
                                perAngle = 360 /viewCount;
                                break;
                            case anticlockwise:
                                perAngle = -360/viewCount;
                                break;
                        }
                        if (mAngle == 360) {
                            mAngle = 0f;
                        }
                        startAnimRotation(mAngle + perAngle, null);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        };
    }

    private GestureDetector.SimpleOnGestureListener getGestureDetectorController() {
        return new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                //杞崲鎴愬姬搴�
                double radians= Math.toRadians(mRotationZ);
                //Math.cos(radians) 杩斿洖瀵瑰簲鐨剅adians寮у害鐨勪綑寮﹀��
                mAngle+=Math.cos(radians)*(distanceX/4) + Math.sin(radians)*(distanceY/4);
                //鍒濆鍖�
                refreshLayout();
                return true;
            }
        };
    }

    /**
     * 鍒濆鍖� 璁$畻骞冲潎瑙掑害鍚庡悇涓瓙view鐨勪綅缃�
     */
    public  void refreshLayout() {
        for (int i=0;i<mCarrouselViews.size();i++){
            double radians = mAngle + 180 - i * 360 / viewCount;
            float x0 = (float) Math.sin(Math.toRadians(radians)) * mCarrouselR;
            float y0 = (float) Math.cos(Math.toRadians(radians)) * mCarrouselR;
            float scale0 = (mDistance - y0) / (mDistance + mCarrouselR);
            mCarrouselViews.get(i).setScaleX(scale0);
            mCarrouselViews.get(i).setScaleY(scale0);
            float rotationX_y = (float) Math.sin(Math.toRadians(mRotationX * Math.cos(Math.toRadians(radians)))) * mCarrouselR;
            float rotationZ_y = -(float) Math.sin(Math.toRadians(-mRotationZ)) * x0;
            float rotationZ_x = (((float) Math.cos(Math.toRadians(-mRotationZ)) * x0) - x0);

            mCarrouselViews.get(i).setTranslationX(x0 + rotationZ_x);
            mCarrouselViews.get(i).setTranslationY(rotationX_y + rotationZ_y);
        }
        List<View> arrayViewList =new ArrayList<>();
        arrayViewList.clear();
        for (int i=0;i<mCarrouselViews.size();i++){
            arrayViewList.add(mCarrouselViews.get(i));
        }
        sortList(arrayViewList);
        postInvalidate();
    }

    /**
     * 鎺掑簭
     * 灏嶅瓙View 鎺掑簭锛岀劧鍚庢牴鎹彉鍖栭�変腑鏄惁閲嶇粯,杩欐牱鏄负浜嗗疄鐜皏iew 鍦ㄦ樉绀虹殑鏃跺�欐潵鎺у埗褰撳墠瑕佹樉绀虹殑鏄摢涓変釜view锛屽彲浠ユ敼鍙樻帓搴忕湅涓嬫晥鏋�
     * @param list
     */
    @SuppressWarnings(\"unchecked\")
    private <T> void sortList(List<View> list) {
        @SuppressWarnings(\"rawtypes\")
        Comparator comparator = new SortComparator();
        T[] array = list.toArray((T[]) new [list.size()]);
        Arrays.sort(array, comparator);
        int i = 0;
        ListIterator<T> it = (ListIterator<T>) list.listIterator();
        while (it.hasNext()) {
            it.next();
            it.set(array[i++]);
        }
        for (int j = 0; j < list.size(); j++) {
            list.get(j).bringToFront();
        }
    }

    /**
     * 绛涢�夊櫒
     */
    private class SortComparator implements Comparator<View> {
        @Override
        public int compare(View o1, View o2) {
            return (int) (1000 * o1.getScaleX() - 1000 * o2.getScaleX());
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        refreshLayout();
        if (mAutoRotation) {
            mHandler.sendEmptyMessageDelayed(CarrouselRotateHandler.mMsgWhat, mHandler.getmRotationTime());
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            checkChildView();
            startAnimationR();
        }
    }

    /**
     * 鏃嬭浆鏈ㄩ┈鍗婂緞鎵撳紑鍔ㄧ敾
     */
    public void startAnimationR() {
        startAnimationR(1f, mCarrouselR);
    }

    /**
     * 鏃嬭浆鏈ㄩ┈鍗婂緞鍔ㄧ敾
     * @param isOpen 鏄惁鎵撳紑  鍚﹀垯鍏抽棴
     */
    public void startAnimationR(boolean isOpen) {
        if (isOpen) {
            startAnimationR(1f, mCarrouselR);
        } else {
            startAnimationR(mCarrouselR, 1f);
        }
    }

    /**
     * 鍗婂緞鎵╂暎銆佹敹缂╁姩鐢� 鏍规嵁璁剧疆鍗婂緞鏉ュ疄鐜�
     * @param from
     * @param to
     */
    public void startAnimationR(float from, float to) {
        mAnimationR = ValueAnimator.ofFloat(from, to);
        mAnimationR.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mCarrouselR = (Float) valueAnimator.getAnimatedValue();
                refreshLayout();
            }
        });
        mAnimationR.setInterpolator(new DecelerateInterpolator());
        mAnimationR.setDuration(2000);
        mAnimationR.start();
    }

    public void checkChildView(){
        //鍏堟竻绌簐iews閲岃竟鍙兘瀛樺湪鐨剉iew闃叉閲嶅
        for (int i = 0; i < mCarrouselViews.size(); i++) {
            mCarrouselViews.remove(i);
        }
        final int count = getChildCount(); //鑾峰彇瀛怴iew鐨勪釜鏁�
        viewCount = count;
        for (int i = 0; i < count; i++) {
            final View view = getChildAt(i); //鑾峰彇鎸囧畾鐨勫瓙view
            final int position = i;
            mCarrouselViews.add(view);
            view.set Listener(new Listener() {
                @Override
                public void (View v) {
                    if(mOnCarrouselItemClickListener!=null){
                        mOnCarrouselItemClickListener.onItemClick(view,position);
                    }
                }
            });

        }

    }

    /**
     * 澶嶄綅
     */
    private void restView() {
        if (viewCount == 0) {
            return;
        }
        float resultAngle = 0;
        //骞冲潎瑙掑害
        float averageAngle = 360 / viewCount;
        if (mAngle < 0) {
            averageAngle = -averageAngle;
        }
        float minvalue = (int) (mAngle / averageAngle) * averageAngle;//鏈�灏忚搴�
        float maxvalue = (int) (mAngle / averageAngle) * averageAngle + averageAngle;//鏈�澶ц搴�
        if (mAngle >= 0) {//鍒嗕负鏄惁灏忎簬0鐨勬儏鍐�
            if (mAngle - mLastAngle > 0) {
                resultAngle = maxvalue;
            } else {
                resultAngle = minvalue;
            }
        } else {
            if (mAngle - mLastAngle < 0) {
                resultAngle = maxvalue;
            } else {
                resultAngle = minvalue;
            }
        }
        startAnimRotation(resultAngle, null);
    }


    /**
     * 鍔ㄧ敾鏃嬭浆
     * @param resultAngle
     * @param complete
     */
    private void startAnimRotation(float resultAngle, final Runnable complete) {
        if (mAngle == resultAngle) {
            return;
        }
        restAnimator = ValueAnimator.ofFloat(mAngle, resultAngle);
        //璁剧疆鏃嬭浆鍖�閫熸彃鍊煎櫒
        restAnimator.setInterpolator(new LinearInterpolator());
        restAnimator.setDuration(300);
        restAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                if (isTouching == false) {
                    mAngle = (Float) animation.getAnimatedValue();
                    refreshLayout();
                }
            }
        });
        restAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimati (Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (isTouching == false) {
                    selectItem = calculateItem();
                    if (selectItem < 0) {
                        selectItem = viewCount + selectItem;
                    }
                    if (mOnCarrouselItemSelectedListener != null) {
                        mOnCarrouselItemSelectedListener.selected(mCarrouselViews.get(selectItem),selectItem);
                    }
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        if (complete != null) {
            restAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimati (Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    complete.run();
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
        }
        restAnimator.start();
    }

    /**
     * 閫氳繃瑙掑害璁$畻鏄鍑犱釜item
     *
     * @return
     */
    private int calculateItem() {
        return (int) (mAngle / (360 / viewCount)) % viewCount;
    }

    /**
     * 瑙︽懜鎿嶄綔
     *
     * @param event
     * @return
     */
    private boolean onTouch(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            mLastAngle = mAngle;
            isTouching = true;
        }
        boolean result = mGestureDetector.onTouchEvent(event);
        if (result) {
            this.getParent().requestDisallowInterceptTouchEvent(true);//閫氱煡鐖舵帶浠跺嬁鎷︽埅鏈帶浠�
        }
        if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
            isTouching = false;
            restView();
            return true;
        }
        return true;
    }


    /**
     * 瑙︽懜鏂规硶
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        setCanAutoRotation(event);
        return true;
    }


    /**
     * 瑙︽懜鍋滄璁℃椂鍣�
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        onTouch(ev);
        setCanAutoRotation(ev);
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 瑙︽懜鏃跺仠姝㈣嚜鍔ㄥ姞杞�
     * @param event
     */
    public void  setCanAutoRotation(MotionEvent event){
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                stopAutoRotation();
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                resumeAutoRotation();
                break;
        }
    }

    /**
     * 鍋滄鑷姩鍔犺浇
     */
    public  void stopAutoRotation(){
        if (mHandler!=null&&mAutoRotation) {
            mHandler.removeMessages(CarrouselRotateHandler.mMsgWhat);
        }
    }

    /**
     * 浠庢柊鍚姩鑷姩鍔犺浇
     */
    public  void resumeAutoRotation(){
        if (mHandler!=null&&mAutoRotation) {
            mHandler.sendEmptyMessageDelayed(CarrouselRotateHandler.mMsgWhat,  mHandler.getmRotationTime());
        }
    }

    /**
     * 鑾峰彇鎵�鏈夌殑view
     *
     * @return
     */
    public List<View> getViews() {
        return mCarrouselViews;
    }

    /**
     * 鑾峰彇瑙掑害
     *
     * @return
     */
    public float getAngle() {
        return mAngle;
    }


    /**
     * 璁剧疆瑙掑害
     *
     * @param angle
     */
    public void setAngle(float angle) {
        this.mAngle = angle;
    }

    /**
     * 鑾峰彇璺濈
     *
     * @return
     */
    public float getDistance() {
        return mDistance;
    }

    /**
     * 璁剧疆璺濈
     *
     * @param distance
     */
    public void setDistance(float distance) {
        this.mDistance = distance;
    }

    /**
     * 鑾峰彇鍗婂緞
     *
     * @return
     */
    public float getR() {
        return mCarrouselR;
    }

    /**
     * 鑾峰彇閫夋嫨鏄鍑犱釜item
     *
     * @return
     */
    public int getSelectItem() {
        return selectItem;
    }

    /**
     * 璁剧疆閫変腑鏂规硶
     *
     * @param selectItem
     */
    public void setSelectItem(int selectItem) {
        if (selectItem >= 0) {
            float angle = 0;
            if (getSelectItem() == 0) {
                if (selectItem == mCarrouselViews.size() - 1) {
                    angle = mAngle - (360 / viewCount);
                } else {
                    angle = mAngle + (360 / viewCount);
                }
            } else if (getSelectItem() == mCarrouselViews.size() - 1) {
                if (selectItem == 0) {
                    angle = mAngle + (360 / viewCount);
                } else {
                    angle = mAngle - (360 / viewCount);
                }
            } else {
                if (selectItem > getSelectItem()) {
                    angle = mAngle + (360 / viewCount);
                } else {
                    angle = mAngle - (360 / viewCount);
                }
            }

            float resultAngle = 0;
            float part = 360 / viewCount;
            if (angle < 0) {
                part = -part;
            }
            //鏈�灏忚搴�
            float minvalue = (int) (angle / part) * part;
            //鏈�澶ц搴�
            float maxvalue = (int) (angle / part) * part;
            if (angle >= 0) {//鍒嗕负鏄惁灏忎簬0鐨勬儏鍐�
                if (angle - mLastAngle > 0) {
                    resultAngle = maxvalue;
                } else {
                    resultAngle = minvalue;
                }
            } else {
                if (angle - mLastAngle < 0) {
                    resultAngle = maxvalue;
                } else {
                    resultAngle = minvalue;
                }
            }

            if (viewCount > 0) startAnimRotation(resultAngle, null);
        }
    }

    /**
     * 璁剧疆鍗婂緞
     *
     * @param r
     */
    public CarrouselLayout setR(float r) {
        this.mCarrouselR = r;
        mDistance = 2f * r;
        return  this;
    }

    /**
     * 閫変腑鍥炶皟鎺ュ彛瀹炵幇
     *
     * @param mOnCarrouselItemSelectedListener
     */
    public void setOnCarrouselItemSelectedListener(OnCarrouselItemSelectedListener mOnCarrouselItemSelectedListener) {
        this.mOnCarrouselItemSelectedListener = mOnCarrouselItemSelectedListener;
    }

    /**
     * 鐐瑰嚮浜嬩欢鍥炶皟
     *
     * @param mOnCarrouselItemClickListener
     */
    public void setOnCarrouselItemClickListener(OnCarrouselItemClickListener mOnCarrouselItemClickListener) {
        this.mOnCarrouselItemClickListener = mOnCarrouselItemClickListener;
    }


    /**
     * 璁剧疆鏄惁鑷姩鍒囨崲
     *
     * @param autoRotation
     */
    public CarrouselLayout setAutoRotation(boolean autoRotation) {
        this.mAutoRotation = autoRotation;
        mHandler.setAutoRotation(autoRotation);
        return this;
    }

    /**
     * 鑾峰彇鑷姩鍒囨崲鏃堕棿
     *
     * @return
     */
    public long getAutoRotationTime() {
        return mHandler.getmRotationTime();
    }

    /**
     * 璁剧疆鑷姩鍒囨崲鏃堕棿闂撮殧
     *
     * @param autoRotationTime
     */
    public CarrouselLayout setAutoRotationTime(long autoRotationTime) {
        if(mHandler!=null)
            mHandler.setmRotationTime(autoRotationTime);
        return this;
    }

    /**
     * 鏄惁鑷姩鍒囨崲
     *
     * @return
     */
    public boolean isAutoRotation() {
        return mAutoRotation;
    }

    /**
     * 璁剧疆鑷姩閫夋嫨鏂瑰悜
     * @param mCarrouselRotateDirection
     * @return
     */
    public CarrouselLayout setAutoScrollDirection(CarrouselRotateDirection mCarrouselRotateDirection) {
        if(mHandler!=null)
            mHandler.setmRotateDirection(mCarrouselRotateDirection);
        return this;
    }

    public void createXAnimation(int from, int to, boolean start){
        if(xAnimation!=null)if(xAnimation.isRunning()==true)xAnimation.cancel();
        xAnimation= ValueAnimator.ofInt(from,to);
        xAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mRotationX= (Integer) animation.getAnimatedValue();
                refreshLayout();
            }
        });
        xAnimation.setInterpolator(new LinearInterpolator());
        xAnimation.setDuration(2000);
        if(start)xAnimation.start();
    }


    public ValueAnimator createZAnimation(int from, int to, boolean start){
        if(zAnimation!=null)if(zAnimation.isRunning()==true)zAnimation.cancel();
        zAnimation= ValueAnimator.ofInt(from,to);
        zAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mRotationZ= (Integer) animation.getAnimatedValue();
                refreshLayout();
            }
        });
        zAnimation.setInterpolator(new LinearInterpolator());
        zAnimation.setDuration(2000);
        if(start)zAnimation.start();
        return zAnimation;
    }

    public CarrouselLayout setRotationX(int mRotationX) {
        this.mRotationX = mRotationX;
        return this;
    }

    public CarrouselLayout setRotationZ(int mRotationZ) {
        this.mRotationZ = mRotationZ;
        return this;
    }

    public float getRotationX() {
        return mRotationX;
    }

    public int getRotationZ() {
        return mRotationZ;
    }

    public ValueAnimator getRestAnimator() {
        return restAnimator;
    }

    public ValueAnimator getAnimationR() {
        return mAnimationR;
    }

    public void setAnimationZ(ValueAnimator zAnimation) {
        this.zAnimation = zAnimation;
    }

    public ValueAnimator getAnimationZ() {
        return zAnimation;
    }

    public void setAnimationX(ValueAnimator xAnimation) {
        this.xAnimation = xAnimation;
    }

    public ValueAnimator getAnimationX() {
        return xAnimation;
    }


}

部分界面非常简单,这里不做,接下来就是在MainActivity中调用自定义布局的方法:

 carrousel.setR(width / 3).setAutoRotation(false)setAutoRotationTime(1500);

设置走马灯的半径,是否自动旋转以及自动旋转的时间间隔

private void addImageView(int res) {
        ImageView image = new ImageView(this);
        image.setImageResource(res);
        carrousel.addView(image);
    }

为走马灯添加图片素材文件

 

\"\"

为走马灯添加监听。

全部代码: https://download.csdn.net/download/qq_32390877/10862090

 

收藏 打印