利用com.graphics.Camera 模拟ViewPager布局3D效果

本文介绍了如何使用自定义View和com.graphics.Camera实现ViewPager的3D布局效果。通过学习坐标系、View绘制、Matrix及Camera原理,开发者可以创建一个具备3D翻转效果的ViewGroup。在AndroidManifest.xml中禁用硬件加速是关键步骤之一。文中推荐了一个学习自定义View的网站,并提供了详细的实现代码和注释。

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

学习了很多自定义View的知识,终于有勇气自己写一个Demo的勇气了,还是要多实践啊!!!!!!!!!

记得在AndroidManifest.xml中关闭硬件加速!!!!!!!

android:hardwareAccelerated="false"
  • 需要掌握的内容:坐标系等基础知识,View的绘制过程,画布的操作,Matrix原理,Matrix Camera原理,事件分发机制等。
    这里我推荐一个网站,里面的内容很丰富也很有趣
    http://www.gcssloop.com/customview/CustomViewIndex

  • 下面介绍如何开始下笔!!!
    首先我们要创建一个类,并继承ViewGroup,规定一些变量,用途都在注释里写了,还有构造函数

    public enum State {
        ToPre, ToNext
    }

    private State state;//标记滚动的状态
    private int mWidth, mHeight;//ViewGroup的宽高
    private float mDownX, mMoveX = 0;//按下的坐标和移动时的坐标
    private VelocityTracker mVelocityTracker;//测速
    private float mAngle = 90;//两个item间的夹角

    private int standerSpeed = 200;//规定的手指移动速率

    private int a = -2500;//阻力

    private Scroller mScroller;
    private Camera mCamera;
    private Matrix mMatrix;

    private boolean flag = false;//标记是否在增加view

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

    public MyView(Context context, AttributeSet attributeSet){
        super(context, attributeSet);
        //这是一个滚动计算器,可以计算出滚动一段距离的中间过程
        mScroller = new Scroller(context);
        mCamera = new Camera();
        mMatrix = new Matrix();
    }

然后就是onLayout onMeasure方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;
        //因为一个ViewGroup里有很多个子布局,这些都在布局文件里写好的,不懂的可以看看源码
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == VISIBLE) {
                child.layout(childLeft,
                        0,
                        childLeft + child.getMeasuredWidth(),
                        child.getMeasuredHeight());
                childLeft = childLeft + child.getMeasuredWidth();
            }
        }
    }

下面开始重头戏,根据手指移动的距离,使ViewGroup滚动起来

@Override
    public boolean onTouchEvent(MotionEvent event) {
        //创建一个速度计算器,可以计算出手指移动时的速率
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        //将这个事件添加到速度计算器中
        mVelocityTracker.addMovement(event);

        //当事件是移动时
        if (event.getAction() == MotionEvent.ACTION_MOVE){
            //拿到当前的坐标x
            float x = event.getX();
            //用之前记录的值减去x,得到移动的距离
            int tempX = (int)(mDownX - x);
            //将当前的x记录下来
            mDownX = x;
            //根据移动的距离tempX滚动ViewGroup
            scrollBy(tempX, 0);
            //根据距离判断首尾view的添加,保证循环滚动
            changeView();
        }else if(event.getAction() == MotionEvent.ACTION_DOWN){
            //如果事件是按下,记录当前按下的坐标x
            mDownX = event.getX();
        }else if(event.getAction() == MotionEvent.ACTION_UP){
            //通过一个速率的单位计算速率的值
            mVelocityTracker.computeCurrentVelocity(1000);
            //往左滑小于0   往右滑大于0
            float xVelocity = mVelocityTracker.getXVelocity();
            //滑动的速度大于规定的速度时,开始自动减速滑动直至静止
            if (xVelocity > standerSpeed) {
                //往前翻滚
                state = State.ToPre;
                //根据重力加速度计算需要滑动的路程
                float s = xVelocity * (-xVelocity / a) / 2;
                //计算需要滑动的时间
                int t = (int) (-xVelocity / a * 2000);
                //开始计算滑动过程
                mScroller.startScroll(0, 0, (int) s, 0, t);
            }else if(xVelocity < -standerSpeed){
                //往后翻滚
                state = State.ToNext;
                //根据重力加速度计算需要滑动的路程
                //消掉速度的单位
                xVelocity = -xVelocity;
                float s = xVelocity * (-xVelocity / a) / 2;
                //计算需要滑动的时间
                int t = (int) (-xVelocity / a * 2000);
                //开始计算滑动过程
                mScroller.startScroll(0, 0, (int) s, 0, t);
            }
        }
        return true;
    }

    /**
     * 这个方法主要是用来在手指抬起后,计算ViewGroup还要再继续滚动多远
     */
    @Override
    public void computeScroll() {
        //当滚动计算器还在计算时,并且现在没有在改变子View的位置
        if (mScroller.computeScrollOffset() && flag == false) {
            if (state == State.ToPre) {
                //拿到滚动计算器计算出的值,并与上一次的值相减,得出应该滚动的距离
                int tempX = (int) (mScroller.getCurrX() - mMoveX);
                //将当前的值记录下来
                mMoveX = mScroller.getCurrX();
                //使ViewGroup滚动-tempX的距离,注意正负方向
                scrollBy(-tempX, 0);
            }else if(state == State.ToNext){
                //拿到滚动计算器计算出的值,并与上一次的值相减,得出应该滚动的距离
                int tempX = (int) (mScroller.getCurrX() - mMoveX);
                //将当前的值记录下来
                mMoveX = mScroller.getCurrX();
                //使ViewGroup滚动tempX的距离,注意正负方向
                scrollBy(tempX, 0);
            }
            //刷新界面
            invalidate();
        }
        //当这一次自动计算结束时,给mMoveX清零
        if(mScroller.isFinished())
            mMoveX = 0;
        //判断一下是否需要在首尾添加界面
        changeView();
    }

    /**
     * 当超过规定位置时,增加首尾的页数,达到拼接的效果
     */
    private void changeView(){
        if (getScrollX() < 20){
            addPre();
            LogUtil.d("TAG", "前面加了一页");
        }else if(getScrollX() > (getChildCount() - 1) * mWidth - 20){
            addNext();
            LogUtil.d("TAG", "后面加了一页");
        }
    }

    /**
     * 把第一个item移动到最后一个item位置
     */
    private void addNext() {
        int childCount = getChildCount();
        View view = getChildAt(0);
        removeViewAt(0);
        addView(view, childCount - 1);
        scrollBy(-mWidth, 0);
    }

    /**
     * 把最后一个item移动到第一个item位置
     */
    private void addPre() {
        int childCount = getChildCount();
        View view = getChildAt(childCount - 1);
        flag = true;
        removeViewAt(childCount - 1);
        addView(view, 0);
        scrollBy(mWidth, 0);
        flag = false;
    }

这时已经实现了循环的左右平移,想要实现3D的效果还需要下面的步骤

    /**
     * 在ViewGroup中需要重写这个方法
     */
    @Override
    protected void dispatchDraw(Canvas canvas) {
        //遍历子View,并画出来
        for (int i = 0; i < getChildCount(); i++) {
            //画3D的方法
            drawScreen(canvas, i, getDrawingTime());
        }
        //如果不想要3D效果只想实现平移,注释掉上面的代码,开启下面的代码即可
//        super.dispatchDraw(canvas);
    }

    /**
     * 画3D效果
     */
    private void drawScreen(Canvas canvas, int i, long drawingTime){
        int curScreenX = mWidth * i;
        //屏幕中不显示的部分不进行绘制
        if (getScrollX() + mWidth < curScreenX) {
            return;
        }
        if (curScreenX < getScrollX() - mWidth) {
            return;
        }

        //计算中心坐标
        float centerX = (getScrollX() > curScreenX) ? curScreenX + mWidth : curScreenX;
        float centerY = mHeight / 2;
        //计算角度
        float degree = mAngle * (getScrollX() - curScreenX) / mWidth;
        if (degree > 90 || degree < -90) {
            return;
        }
        canvas.save();

        //利用Camera进行一次旋转拍照
        mCamera.save();
        mCamera.rotateY(-degree);
        //并将结果保存在Matrix矩阵中
        mCamera.getMatrix(mMatrix);
        mCamera.restore();

        //将矩阵移动到视图中心位置
        mMatrix.preTranslate(-centerX, -centerY);
        mMatrix.postTranslate(centerX, centerY);
        //将矩阵设置到画布上
        canvas.concat(mMatrix);
        //画子View
        drawChild(canvas, getChildAt(i), drawingTime);
        canvas.restore();
    }

现在已经可以实现3D的效果了,实现的过程我写了很清楚的注释。主要实现3D的效果还是靠Camera和Matrix类,具体的原理还是需要自己去研究了。

源码:https://github.com/Techck/3DFlipViewPager

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值