自定义Rotate3dView

自定义Rotate3dView

本文地址,转载请注明:https://blog.csdn.net/BenjaminFFF/article/details/82828289
github地址:https://github.com/BenjaminFF/Rotate3dView

知识准备

  • Android自定义ViewGroup的知识。
  • Android事件传递机制。
  • Android Camera。
  • Android属性动画

Android Camera参考GscSloop的这篇文章
Android属性动画参考郭霖的这篇文章

效果:

在这里插入图片描述

初始化View

准备两个View,一个正面,一个反面。然后添加到Rotate3dView里面。这里模仿了RecyclerView的Adapter,思路如下:
先定义一个抽象类,里面有一些抽象方法:

public abstract static class Adapter<FrontVH extends ViewHolder,BackVH extends ViewHolder>{
        public abstract FrontVH onCreateFrontViewHolder(ViewGroup parent);
        public abstract void onBindFrontViewHolder(FrontVH holder);

        public abstract BackVH onCreateBackViewHolder(ViewGroup parent);
        public abstract void onBindBackViewHolder(BackVH holder);
        
        ......
}

    public abstract static class ViewHolder{
        public View itemView;

        public ViewHolder(View itemView) {
            this.itemView = itemView;
        }
    }

通过实现Adapter和它里面的这些方法,起到载入数据的作用。

然后在setAdapter里面做功夫:

private void addViewFromAdapter(){
        ViewHolder frontHolder=mAdapter.CreateFrontViewHolder(this);
        mAdapter.onBindFrontViewHolder(frontHolder);

        ViewHolder backHolder=mAdapter.CreateBackViewHolder(this);
        mAdapter.onBindBackViewHolder(backHolder);

        addView(frontHolder.itemView);
        addView(backHolder.itemView);
    }
public void setAdapter(Adapter adapter){
        ......
        mAdapter=adapter;
        addViewFromAdapter();
        requestLayout();
    }    

通过AddView将绑定好数据的View添加进去,然后再调用requestLayout(),View重新测量,布局和重绘。

接下来就是测量和布局了。
这里测量和布局没有多考虑和研究,没有支持Margin,就默认设置前后页面都填充父布局。

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for(int i=0;i<2;i++){
            View childView=getChildAt(i);
            childView.layout(0,0,childView.getMeasuredWidth(),childView.getMeasuredHeight());
        }
    }

重点来了:dispatchDraw
ViewGroup会在dispathDraw里面调用子View的onDraw,然后我们可以通过重写dispatchDraw来改变子View的Canvas。初始化的时候,frontView没有变化,backView要绕Y轴旋转180度。而且要先画backView,后画frontView。**因为后画的View才会覆盖前面的View。**绕Y轴旋转180度就可以用Camera来实现。

private void initChildViews(Canvas canvas){
        //先画childView2
        canvas.save();
        View childView2=getChildAt(1);
        Matrix matrix=childView2.getMatrix();
        Camera camera=new Camera();
        camera.save();
        camera.rotateY(180);
        camera.getMatrix(matrix);
        camera.restore();

        matrix.preTranslate(-mWidth/2, -mHeight/2);
        matrix.postTranslate(mWidth/2, mHeight/2);

        canvas.concat(matrix);
        drawChild(canvas,childView2,getDrawingTime());
        canvas.restore();

        //再画childView1
        canvas.save();
        View childView1=getChildAt(0);
        drawChild(canvas,childView1,getDrawingTime());
        canvas.restore();
    }

这样就初始化好了Rotate3dView,并且它上面的数据已经所有装载进去。

Rotate3dView的旋转动画

我们通过左滑或者右滑来旋转View,所以先要重写onInterceptTouchEvent,在里面判断是滑动还是点击。如果是滑动,就return true告诉childView这个事件我来处理。这里加入一个isRotating判断,就是告诉childView在旋转的时候你也不能点击。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mXDown = ev.getRawX();
                break;
            case MotionEvent.ACTION_MOVE:
                mXMove = ev.getRawX();
                float diff = Math.abs(mXMove - mXDown);
                // 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件
                if (diff > mTouchSlop||isRotating) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

我告诉childView我要处理左滑或者右滑事件,那我就重写onTouchEvent来处理。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mXDown=event.getRawX();
                break;
            case MotionEvent.ACTION_UP:
                //Log.i("ACTION_UP","ACTION_UP");
                mXUp=event.getRawX();
                if(Math.abs(mXUp-mXDown)>mTouchSlop&&!isRotating) {
                    if (mXUp - mXDown > 0) {      //从左向右滑动,逆时针旋转
                        antiClockWised = true;
                        startRotateAnimation();
                        Log.i("ACTION_UP", "ACTION_UP");
                    } else {
                        antiClockWised = false;
                        Log.i("ACTION_UP", "ACTION_UP");
                        startRotateAnimation();
                    }
                }
        }
        return true;
    }

先用mXup-mXDown来判断左滑还是右滑,左滑就顺时针旋转。然后调用startRotateAnimation()。return true可以让ACTION_DOWN到ACTION_UP一直执行。

private void startRotateAnimation(){     //reverse==true代表逆时针旋转
        ValueAnimator valueAnimator;
        if(antiClockWised){
            valueAnimator=ValueAnimator.ofFloat(0,180);
        }else {
            valueAnimator=ValueAnimator.ofFloat(0,-180);
        }
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAngle=(float)animation.getAnimatedValue();
                invalidate();
                if(mAngle==180||mAngle==-180){
                    isRotating=false;
                    if(frontReversed){
                        frontReversed=false;
                        getChildAt(1).setVisibility(INVISIBLE);
                        getChildAt(0).setVisibility(VISIBLE);
                    }else {
                        frontReversed=true;
                        getChildAt(0).setVisibility(INVISIBLE);
                        getChildAt(1).setVisibility(VISIBLE);
                    }
                    mAngle=0;
                }
            }
        });
        valueAnimator.setDuration(duration);
        valueAnimator.start();
        isRotating=true;
    }

这里用了ValueAnimator这个属性动画,它有一个监听器onAnimationUpdate,我在里面得到在特定时刻旋转的角度,然后invalidate()重绘,把角度给dispatchDraw,如果角度是180度或者-180度,代表动画结束。
这里设置setVisibility是为了禁止背面View和它的child的所有事件,才开始是想通过递归来禁止,后来发现了这个方法,现在还是没有出现什么问题。

开始使用

一切都跟RecyclerView太相像了。

先在res的layout里面定义item_back.xm和item_front.xml,然后写一个Adapter。

public class Rotate3dViewAdapter extends Rotate3dView.Adapter<Rotate3dViewAdapter.FrontHolder,Rotate3dViewAdapter.BackHolder> {
    Context mContext;
    public Rotate3dViewAdapter(Context context) {
        mContext=context;
    }

    public class FrontHolder extends Rotate3dView.ViewHolder{
        TextView mTextView;
        Button button;
        public FrontHolder(View itemView) {
            super(itemView);
            mTextView=itemView.findViewById(R.id.item_front_text);
            button=itemView.findViewById(R.id.front_button);
        }
    }

    public class BackHolder extends Rotate3dView.ViewHolder{
        ......
    }

    @Override
    public FrontHolder onCreateFrontViewHolder(ViewGroup parent) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_front, parent, false);
        FrontHolder frontHolder=new FrontHolder(v);
        return frontHolder;
    }

    @Override
    public void onBindFrontViewHolder(FrontHolder holder) {
        holder.mTextView.setText("FRONT");
        holder.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext,"Front Button Clicked",Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public BackHolder onCreateBackViewHolder(ViewGroup parent) {
          .....
    }

    @Override
    public void onBindBackViewHolder(BackHolder holder) {
          ......
    }
}

然后在某个布局里面加入它,注意父亲布局要来个android:clipChildren=“false”,可以让Rotate3dView Overflow。

<com.benjamin.mylib.Rotate3dView
            android:id="@+id/rotate3dView"
            android:layout_margin="60dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </com.benjamin.mylib.Rotate3dView>

然后在Activity或是Fragment里面:

Rotate3dView rotate3dView=findViewById(R.id.rotate3dView);
        rotate3dView.setDuration(1000);
        Rotate3dViewAdapter adapter=new Rotate3dViewAdapter(this);
        rotate3dView.setAdapter(adapter);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于谷歌官方提供的3D翻转示例进行修改,修复了在不同设备上显示效果差异过大的问题。项目地址:https://github.com/GcsSloop/Rotate3dAnimation 效果图:如何使用:// 计算中心点(这里是使用view的中心作为旋转的中心点)         final float centerX = view.getWidth() / 2.0f;                 final float centerY = view.getHeight() / 2.0f;        //括号内参数分别为(上下文,开始角度,结束角度,x轴中心点,y轴中心点,深度,是否扭曲)         final Rotate3dAnimation rotation = new Rotate3dAnimation(this, start, end, centerX, centerY, 1.0f, true);         rotation.setDuration(1500);                               //设置动画时长         rotation.setFillAfter(true);                              //保持旋转后效果         rotation.setInterpolator(new AccelerateInterpolator());   //设置插值器         rotation.setAnimationListener(new AnimationListener() {   //设置监听器             @Override             public void onAnimationStart(Animation animation) {             }            @Override             public void onAnimationRepeat(Animation animation) {             }            @Override             public void onAnimationEnd(Animation animation) {             }         });         view.startAnimation(rotation);                            //开始动画
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值