自定义3D效果的轮转控件

简介

近期项目中需要类似banner效果,但是又有一个点不一样,表达很生硬,直接看效果图如下。

自定义3D效果的轮转控件_第1张图片
未标题-1.gif

实现

通过效果图,要实现如上效果首先想到的是自定义控件,考虑到扩展我们要加入属性进行控制,自定义属性:展示方向、是否自动轮转等。 那么我们接下来就看如何实现。步骤分:

  1. 自定义属性
  2. 获取属性并初始化
  3. 实现手势及动画
  4. 调用

一、自定义属性


    
    

    
        
            
            
        
        
        

        
            
            
        

    

二、获取属性

public LoopRotarySwitchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        //获取自定义属性
        TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.LoopRotarySwitchView);
        mOrientation=typedArray.getInt(R.styleable.LoopRotarySwitchView_orientation,horizontal);
        autoRotation=typedArray.getBoolean(R.styleable.LoopRotarySwitchView_autoRotation,false);
        r=typedArray.getDimension(R.styleable.LoopRotarySwitchView_r,LoopR);
        int direction=typedArray.getInt(R.styleable.LoopRotarySwitchView_direction,0);
        typedArray.recycle();
        //初始化手势
        mGestureDetector = new GestureDetector(context, getGeomeryController());
        //设置展示方向
        if(mOrientation==horizontal){//如果是水平 z值为0  如果是竖直z值为90
            loopRotationZ=0;
        }else{
            loopRotationZ=90;
        }
        if(direction==0){//设置自定滚动的方向
            autoRotatinDirection= AutoScrollDirection.left;
        }else{
            autoRotatinDirection= AutoScrollDirection.right;
        }
        loopHandler.setLoop(autoRotation);
    }

三、实现手势和动画

 /**
     * 手势
     *
     * @return
     */
    private GestureDetector.SimpleOnGestureListener getGeomeryController() {
        return new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                angle+=Math.cos(Math.toRadians(loopRotationZ))*(distanceX/views.size())
                        +Math.sin(Math.toRadians(loopRotationZ))*(distanceY/views.size());
                initView();
                return true;
            }
        };
    }

    private void initView() {
        for (int i = 0; i < views.size(); i++) {
            double radians=angle+180- i * 360 / size;
            float x0 = (float)Math.sin(Math.toRadians(radians)) * r;
            float y0 = (float)Math.cos(Math.toRadians(radians)) * r;
            float  scale0 = (distance - y0) / (distance + r);//计算子view之间的比例,可以看到distance越大的话 比例越小,也就是大小就相差越小
            views.get(i).setScaleX(scale0);//对view进行缩放
            views.get(i).setScaleY(scale0);//对view进行缩放
            float rotationX_y=(float)Math.sin(Math.toRadians(loopRotationX *Math.cos(Math.toRadians(radians))))*r;
            float rotationZ_y=-(float)Math.sin(Math.toRadians(-loopRotationZ))*x0;
            float rotationZ_x=(((float) Math.cos(Math.toRadians(-loopRotationZ))*x0)-x0);
            views.get(i).setTranslationX( x0 +rotationZ_x);
            views.get(i).setTranslationY(rotationX_y+rotationZ_y);
        }
        List arrayViewList =new ArrayList<>();
        arrayViewList.clear();
        for (int i=0;i

四、调用

调用和其他自定义控件一样,全路径名称然后设置属性。



    

  private String[] titles = {"闹钟","实时监测","阿尔法"};
    private List views = new ArrayList<>();
    private LoopRotarySwitchView loopRotarySwitchView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_more);
        initWidget();
        initData();
        initLoopRotarySwitchView();
    }

    private void initWidget() {
        loopRotarySwitchView = (LoopRotarySwitchView) findViewById(R.id.loop_view);
    }

    private void initData(){
        View view = LayoutInflater.from(this).inflate(R.layout.item_head_layout, null);
        View view1 = LayoutInflater.from(this).inflate(R.layout.item_head_layout, null);
        View view2 = LayoutInflater.from(this).inflate(R.layout.item_head_layout, null);
        views.add(view);
        views.add(view1);
        views.add(view2);
        for (int i=0;i

最后放上整个自定义控件的代码

public class LoopRotarySwitchView extends RelativeLayout {

    private final static  int LoopR = 200;

    private final static  int  vertical=0;//竖直

    private final static  int  horizontal=1;//水平

    private  int mOrientation=horizontal;//方向

    private Context mContext;//上下文

    private ValueAnimator restAnimator = null;//回位动画

    private ValueAnimator rAnimation = null;//半径动画

    private ValueAnimator zAnimation=null;

    private ValueAnimator xAnimation=null;

    private int loopRotationX =0, loopRotationZ =0;//x轴旋转和轴旋转,y轴无效果

    private GestureDetector mGestureDetector = null;//手势类

    private int selectItem = 0;//当前选择项

    private int size = 0;//个数

    private float r = LoopR;//半径

    private float multiple = 2f;//倍数

    private float distance = multiple * r;//camera和观察的旋转物体距离, 距离越长,最大物体和最小物体比例越不明显

    private float angle = 0;    //旋转的角度

    private float last_angle = 0;   //最后的角度,用来记录上一次取消touch之后的角度

    private boolean autoRotation = false;//自动旋转

    private boolean touching = false;//正在触摸

    private AutoScrollDirection autoRotatinDirection = AutoScrollDirection.left; //默认自动滚动是从右往左

    private List views = new ArrayList();//子view引用列表

    private OnItemSelectedListener onItemSelectedListener = null;//选择事件接口

    private OnLoopViewTouchListener onLoopViewTouchListener = null;//选择事件接口

    private OnItemClickListener onItemClickListener = null;//被点击的回调

    private boolean isCanClickListener=true;//是否可以点击回调

    private float x;//移动的x是否符合回调点击事件

    private float limitX=30;//滑动倒最低30

    public enum AutoScrollDirection{
        left,right
    }
    /**
     * 构造方法
     *
     * @param context
     */
    public LoopRotarySwitchView(Context context) {
        this(context,null);
    }

    /**
     * 构造方法
     *
     * @param context
     * @param attrs
     */
    public LoopRotarySwitchView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    /**
     * 构造方法
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public LoopRotarySwitchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        //获取自定义属性
        TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.LoopRotarySwitchView);
        mOrientation=typedArray.getInt(R.styleable.LoopRotarySwitchView_orientation,horizontal);
        autoRotation=typedArray.getBoolean(R.styleable.LoopRotarySwitchView_autoRotation,false);
        r=typedArray.getDimension(R.styleable.LoopRotarySwitchView_r,LoopR);
        int direction=typedArray.getInt(R.styleable.LoopRotarySwitchView_direction,0);
        typedArray.recycle();
        //初始化手势
        mGestureDetector = new GestureDetector(context, getGeomeryController());
        //设置展示方向
        if(mOrientation==horizontal){//如果是水平 z值为0  如果是竖直z值为90
            loopRotationZ=0;
        }else{
            loopRotationZ=90;
        }
        if(direction==0){//设置自定滚动的方向
            autoRotatinDirection= AutoScrollDirection.left;
        }else{
            autoRotatinDirection= AutoScrollDirection.right;
        }
        loopHandler.setLoop(autoRotation);
    }


    /**
     * handler处理
     */
    LoopRotarySwitchViewHandler loopHandler = new LoopRotarySwitchViewHandler(3000) {
        @Override
        public void doScroll() {
            try {
                if (size != 0) {//判断自动滑动从那边开始
                    int perAngle = 0;
                    switch (autoRotatinDirection)
                    {
                        case left:
                            perAngle = 360 /size;
                            break;
                        case right:
                            perAngle = -360/size;
                            break;
                    }
                    if (angle == 360) {
                        angle = 0f;
                    }
                    AnimRotationTo(angle + perAngle, null);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    /**
     * 排序
     * 對子View 排序,然后根据变化选中是否重绘,这样是为了实现view 在显示的时候来控制当前要显示的是哪三个view,可以改变排序看下效果
     * @param list
     */
    @SuppressWarnings("unchecked")
    private  void sortList(List list) {

        @SuppressWarnings("rawtypes")
        Comparator comparator = new SortComparator();
        T[] array = list.toArray((T[]) new Object[list.size()]);

        Arrays.sort(array, comparator);
        int i = 0;
        ListIterator it = (ListIterator) 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 {
        @Override
        public int compare(View lhs, View rhs) {
            int result = 0;
            try {
                result = (int) (1000 * lhs.getScaleX() - 1000 * rhs.getScaleX());
            } catch (Exception e) {
            }
            return result;
        }
    }

    /**
     * 手势
     *
     * @return
     */
    private GestureDetector.SimpleOnGestureListener getGeomeryController() {
        return new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                angle+=Math.cos(Math.toRadians(loopRotationZ))*(distanceX/views.size())
                        +Math.sin(Math.toRadians(loopRotationZ))*(distanceY/views.size());
                initView();
                return true;
            }
        };
    }

    private void initView() {
        for (int i = 0; i < views.size(); i++) {
            double radians=angle+180- i * 360 / size;
            float x0 = (float)Math.sin(Math.toRadians(radians)) * r;
            float y0 = (float)Math.cos(Math.toRadians(radians)) * r;
            float  scale0 = (distance - y0) / (distance + r);//计算子view之间的比例,可以看到distance越大的话 比例越小,也就是大小就相差越小
            views.get(i).setScaleX(scale0);//对view进行缩放
            views.get(i).setScaleY(scale0);//对view进行缩放
            float rotationX_y=(float)Math.sin(Math.toRadians(loopRotationX *Math.cos(Math.toRadians(radians))))*r;
            float rotationZ_y=-(float)Math.sin(Math.toRadians(-loopRotationZ))*x0;
            float rotationZ_x=(((float) Math.cos(Math.toRadians(-loopRotationZ))*x0)-x0);
            views.get(i).setTranslationX( x0 +rotationZ_x);
            views.get(i).setTranslationY(rotationX_y+rotationZ_y);
        }
        List arrayViewList =new ArrayList<>();
        arrayViewList.clear();
        for (int i=0;i= 0) {//分为是否小于0的情况
            if (angle - last_angle > 0) {
                finall = maxvalue;
            } else {
                finall = minvalue;
            }
        } else {
            if (angle - last_angle < 0) {
                finall = maxvalue;
            } else {
                finall = minvalue;
            }
        }
        AnimRotationTo(finall, null);
    }


    /**
     * 动画
     *
     * @param finall
     * @param complete
     */
    private void AnimRotationTo(float finall, final Runnable complete) {
        if (angle == finall) {//如果相同说明不需要旋转
            return;
        }
        restAnimator = ValueAnimator.ofFloat(angle, finall);
        restAnimator.setInterpolator(new DecelerateInterpolator());//设置旋转减速插值器
        restAnimator.setDuration(300);

        restAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                if (touching == false) {
                    angle = (Float) animation.getAnimatedValue();
                    initView();
                }
            }
        });
        restAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (touching == false) {
                    selectItem = calculateItem();
                    if (selectItem < 0) {
                        selectItem = size + selectItem;
                    }
                    if (onItemSelectedListener != null) {
                        onItemSelectedListener.selected(selectItem, views.get(selectItem));
                    }
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

        if (complete != null) {
            restAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(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 Math.abs((int) (angle / (360 / size)) % size);
    }

    /**
     * 触摸操作
     *
     * @param event
     * @return
     */
    private boolean onTouch(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            last_angle = angle;
            touching = true;
        }
        boolean sc = mGestureDetector.onTouchEvent(event);
        if (sc) {
            this.getParent().requestDisallowInterceptTouchEvent(true);//通知父控件勿拦截本控件
        }
        if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
            touching = false;
            restPosition();
            return true;
        }
        return true;
    }


    /**
     * 触摸方法
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (onLoopViewTouchListener != null) {
            onLoopViewTouchListener.onTouch(event);
        }
        isCanClickListener(event);
        return true;
    }


    /**
     * 触摸停止计时器,抬起设置可下啦刷新
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        onTouch(ev);
        if (onLoopViewTouchListener != null) {
            onLoopViewTouchListener.onTouch(ev);
        }
        isCanClickListener(ev);
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 是否可以点击回调
     * @param event
     */
    public void  isCanClickListener(MotionEvent event){
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                x = event.getX();
                if (autoRotation) {
                    loopHandler.removeMessages(LoopRotarySwitchViewHandler.msgid);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (autoRotation) {
                    loopHandler.sendEmptyMessageDelayed(LoopRotarySwitchViewHandler.msgid,  loopHandler.loopTime);
                }
                if (event.getX() - x > limitX || x - event.getX() > limitX) {
                    isCanClickListener = false;
                } else {
                    isCanClickListener = true;
                }
                break;
        }
    }
    /**
     * 获取所有的view
     *
     * @return
     */
    public List getViews() {
        return views;
    }

    /**
     * 获取角度
     *
     * @return
     */
    public float getAngle() {
        return angle;
    }


    /**
     * 设置角度
     *
     * @param angle
     */
    public void setAngle(float angle) {
        this.angle = angle;
    }

    /**
     * 获取距离
     *
     * @return
     */
    public float getDistance() {
        return distance;
    }

    /**
     * 设置距离
     *
     * @param distance
     */
    public void setDistance(float distance) {
        this.distance = distance;
    }

    /**
     * 获取半径
     *
     * @return
     */
    public float getR() {
        return r;
    }

    /**
     * 获取选择是第几个item
     *
     * @return
     */
    public int getSelectItem() {
        return selectItem;
    }

    /**
     * 设置选中方法
     *
     * @param selectItem
     */
    public void setSelectItem(int selectItem) {

        if (selectItem >= 0) {
            float jiaodu = 0;
            if (getSelectItem() == 0) {
                if (selectItem == views.size() - 1) {
                    jiaodu = angle - (360 / size);
                } else {
                    jiaodu = angle + (360 / size);
                }
            } else if (getSelectItem() == views.size() - 1) {
                if (selectItem == 0) {
                    jiaodu = angle + (360 / size);
                } else {
                    jiaodu = angle - (360 / size);
                }
            } else {
                if (selectItem > getSelectItem()) {
                    jiaodu = angle + (360 / size);
                } else {
                    jiaodu = angle - (360 / size);
                }
            }

            float finall = 0;
            float part = 360 / size;//一份的角度
            if (jiaodu < 0) {
                part = -part;
            }
            float minvalue = (int) (jiaodu / part) * part;//最小角度
            float maxvalue = (int) (jiaodu / part) * part;//最大角度
            if (jiaodu >= 0) {//分为是否小于0的情况
                if (jiaodu - last_angle > 0) {
                    finall = maxvalue;
                } else {
                    finall = minvalue;
                }
            } else {
                if (jiaodu - last_angle < 0) {
                    finall = maxvalue;
                } else {
                    finall = minvalue;
                }
            }

            if (size > 0) AnimRotationTo(finall, null);
        }
    }

    /**
     * 设置半径
     *
     * @param r
     */
    public LoopRotarySwitchView setR(float r) {
        this.r = r;
        distance = multiple * r;
        return  this;
    }

    /**
     * 选中回调接口实现
     *
     * @param onItemSelectedListener
     */
    public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
        this.onItemSelectedListener = onItemSelectedListener;
    }

    /**
     * 点击事件回调
     *
     * @param onItemClickListener
     */
    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    /**
     * 触摸时间回调
     *
     * @param onLoopViewTouchListener
     */
    public void setOnLoopViewTouchListener(OnLoopViewTouchListener onLoopViewTouchListener) {
        this.onLoopViewTouchListener = onLoopViewTouchListener;
    }

    /**
     * 设置是否自动切换
     *
     * @param autoRotation
     */
    public LoopRotarySwitchView setAutoRotation(boolean autoRotation) {
        this.autoRotation = autoRotation;
        loopHandler.setLoop(autoRotation);
        return this;
    }

    /**
     * 获取自动切换时间
     *
     * @return
     */
    public long getAutoRotationTime() {
        return loopHandler.loopTime;
    }

    /**
     * 设置自动切换时间间隔
     *
     * @param autoRotationTime
     */
    public LoopRotarySwitchView setAutoRotationTime(long autoRotationTime) {
        loopHandler.setLoopTime(autoRotationTime);
        return this;
    }

    /**
     * 是否自动切换
     *
     * @return
     */
    public boolean isAutoRotation() {
        return autoRotation;
    }

    /**
     * 设置倍数
     *
     * @param mMultiple 设置这个必须在setR之前调用,否则无效
     * @return
     */
    public LoopRotarySwitchView setMultiple(float mMultiple) {
        this.multiple = mMultiple;
        return this;
    }

    public LoopRotarySwitchView setAutoScrollDirection(AutoScrollDirection mAutoScrollDirection) {
        this.autoRotatinDirection = mAutoScrollDirection;
        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) {
                loopRotationX= (Integer) animation.getAnimatedValue();
                initView();
            }
        });
        xAnimation.setInterpolator(new DecelerateInterpolator());
        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) {
                loopRotationZ= (Integer) animation.getAnimatedValue();
                initView();
            }
        });
        zAnimation.setInterpolator(new DecelerateInterpolator());
        zAnimation.setDuration(2000);
        if(start)zAnimation.start();
        return zAnimation;
    }

    /**
     * 设置方向
     * @param mOrientation
     * @return
     */
    public   LoopRotarySwitchView setOrientation(int mOrientation){
        setHorizontal(mOrientation==horizontal,false);
        return this;
    }
    public LoopRotarySwitchView setHorizontal(boolean horizontal,boolean anim) {
        if(anim){
            if(horizontal){
                createZAnimation(getLoopRotationZ(),0,true);
            }else{
                createZAnimation(getLoopRotationZ(),90,true);
            }
        }else{
            if(horizontal){
                setLoopRotationZ(0);
            }else {
                setLoopRotationZ(90);
            }
            initView();
        }
        return  this;
    }

    public LoopRotarySwitchView setLoopRotationX(int loopRotationX) {
        this.loopRotationX = loopRotationX;
        return this;
    }

    public LoopRotarySwitchView setLoopRotationZ(int loopRotationZ) {
        this.loopRotationZ = loopRotationZ;
        return this;
    }

    public int getLoopRotationX() {
        return loopRotationX;
    }

    public int getLoopRotationZ() {
        return loopRotationZ;
    }

    public ValueAnimator getRestAnimator() {
        return restAnimator;
    }

    public ValueAnimator getrAnimation() {
        return rAnimation;
    }

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

    public ValueAnimator getzAnimation() {
        return zAnimation;
    }

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

    public ValueAnimator getxAnimation() {
        return xAnimation;
    }
}

你可能感兴趣的:(自定义3D效果的轮转控件)