Android从零开搞系列:自定义View(15)仿天天美剧拖动卡片的效果(下)

来源:互联网 发布:小米净水器怎么样 知乎 编辑:程序博客网 时间:2024/06/09 15:15

转载请注意:http://blog.csdn.net/wjzj000/article/details/73441173

本菜开源的一个自己写的Demo,希望能给Androider们有所帮助,水平有限,见谅见谅…
https://github.com/zhiaixinyang/PersonalCollect (拆解GitHub上的优秀框架于一体,全部拆离不含任何额外的库导入)
https://github.com/zhiaixinyang/MyFirstApp(Retrofit+RxJava+MVP)

写在前面

本来想昨天就把这个效果分析完毕。然后事与愿违,随便玩了玩时间就没了。没办法只有今天补上….最难逃儿女情长书接上回楚霸王,让我们续上仿天天美剧拖动卡片的效果(上):
http://blog.csdn.net/wjzj000/article/details/73432852

开始

我们上部分分析完了setAdapter的一系列的过程,这部分我们要通过FlingCardListener来近距离的看一看我们拖动Card的实现效果。


在开始FlingCardListener之前,让我们回过来上看一个类LinearRegression。可能各位看官已经把这个类给忘了,所以我们在分析的一开始就看一看它。

LinearRegression

    public LinearRegression(float[] x, float[] y) {        if (x.length != y.length) {            throw new IllegalArgumentException("array lengths are not equal");        }        N = x.length;        //求平均值        double sumx = 0.0, sumy = 0.0, sumx2 = 0.0;        for (int i = 0; i < N; i++) sumx  += x[i];        for (int i = 0; i < N; i++) sumx2 += x[i]*x[i];        for (int i = 0; i < N; i++) sumy  += y[i];        double xbar = sumx / N;        double ybar = sumy / N;        //求方差        double xxbar = 0.0, yybar = 0.0, xybar = 0.0;        for (int i = 0; i < N; i++) {            xxbar += (x[i] - xbar) * (x[i] - xbar);            yybar += (y[i] - ybar) * (y[i] - ybar);            xybar += (x[i] - xbar) * (y[i] - ybar);        }        beta  = xybar / xxbar;        alpha = ybar - beta * xbar;        //一些计算公式        double rss = 0.0;        double ssr = 0.0;        for (int i = 0; i < N; i++) {            double fit = beta*x[i] + alpha;            rss += (fit - y[i]) * (fit - y[i]);            ssr += (fit - ybar) * (fit - ybar);        }        int degreesOfFreedom = N-2;        R2    = ssr / yybar;        svar  = rss / degreesOfFreedom;        svar1 = svar / xxbar;        svar0 = svar/N + xbar*xbar*svar1;    }

FlingCardListener

这里比较重要的方法是onTouch()方法,构造方法传参非常多,各个变量的使用将逐一在onTouch()方法这种展开。因此接下来让我们看一看onTouch()方法。

    @Override    public boolean onTouch(View view, MotionEvent event) {        switch (event.getAction() & MotionEvent.ACTION_MASK) {            case MotionEvent.ACTION_DOWN:                //确定唯一的坐标点,避免多指操作带来的问题                mActivePointerId = event.getPointerId(0);                float x = 0;                float y = 0;                boolean success = false;                try {                    x = event.getX(mActivePointerId);                    y = event.getY(mActivePointerId);                    success = true;                } catch (IllegalArgumentException e) {                    Log.w(TAG, "Exception in onTouch(view, event) : " + mActivePointerId, e);                }                if (success) {                    //记住我们按下时的坐标                    aDownTouchX = x;                    aDownTouchY = y;                    //设置View的初始坐标是传入frame的X、Y值                    if (aPosX == 0) {                        aPosX = frame.getX();                    }                    if (aPosY == 0) {                        aPosY = frame.getY();                    }                    //记录按压位置状态                    if (y < objectH / 2) {                        touchPosition = TOUCH_ABOVE;                    } else {                        touchPosition = TOUCH_BELOW;                    }                }                view.getParent().requestDisallowInterceptTouchEvent(true);                break;            case MotionEvent.ACTION_UP:                mActivePointerId = INVALID_POINTER_ID;                //将在下文展开                resetCardViewOnStack();                view.getParent().requestDisallowInterceptTouchEvent(false);                break;            case MotionEvent.ACTION_MOVE:                final int pointerIndexMove = event.findPointerIndex(mActivePointerId);                final float xMove = event.getX(pointerIndexMove);                final float yMove = event.getY(pointerIndexMove);                final float dx = xMove - aDownTouchX;                final float dy = yMove - aDownTouchY;                aPosX += dx;                aPosY += dy;                /**                 * objectX= frame.getX()                 * 此处为计算移动距离                  */                float distobjectX = aPosX - objectX;                /**                 * 如果我们仔细看Card的拖动效果可以发现,                 * 有一个小小的旋转效果如果我们仔细看Card的拖动效果可以发现,有一个小小的旋转效果                 *                 * 此处是计算旋转角度自定义的公式,当拖动X轴距离和parentWidth相等时,                 * 那么最大旋转的效果就是:BASE_ROTATION_DEGREES * 2.f                 */                float rotation = BASE_ROTATION_DEGREES * 2.f * distobjectX / parentWidth;                if (touchPosition == TOUCH_BELOW) {                    rotation = -rotation;                }                //设置Card的位置,旋转等                frame.setX(aPosX);                frame.setY(aPosY);                frame.setRotation(rotation);                //onScroll方法调用                mFlingListener.onScroll(getScrollProgressPercent());                break;        }        return true;    }

这里的思路是通过setListener的时候,new FlingCardListener把每一个Card的View对象和Adapter中对应的数据,FlingCardListener将通过onTouch()方法中的坐标变换进行对Card
位置进行设置,并且完成回调。


resetCardViewOnStack()

    private boolean resetCardViewOnStack() {        //如果超出左边界限        if (movedBeyondLeftBorder()) {            // 执行左划出效果,相关执行代码在这个方法中( onSelected()在下文展开 )            // getExitPoint()得到离开时的坐标Y的方法            onSelected(true, getExitPoint(-objectW), 100);            mFlingListener.onScroll(-1.0f);        } else if (movedBeyondRightBorder()) {            // 执行右划出效果            onSelected(false, getExitPoint(parentWidth), 100);            mFlingListener.onScroll(1.0f);        } else {            //如果不从左或右划出,那么便是Card回弹            float abslMoveDistance = Math.abs(aPosX - objectX);            aPosX = 0;            aPosY = 0;            aDownTouchX = 0;            aDownTouchY = 0;            frame.animate()                    .setDuration(200)                    .setInterpolator(new OvershootInterpolator(1.5f))                    .x(objectX)                    .y(objectY)                    .rotation(0);            mFlingListener.onScroll(0.0f);            if (abslMoveDistance < 4.0) {                mFlingListener.onClick(dataObject);            }        }        return false;    }

onSelected()

Card划出的代码实现,需要接受exitY,离开的坐标Y

    public void onSelected(final boolean isLeft,float exitY, long duration) {        isAnimationRunning = true;        float exitX;        if (isLeft) {            /**             * 计算离开点的X坐标(左移动为减,右移动为加)             * getRotationWidthOffset():             *         objectW / MAX_COS - objectW(计算旋转的宽度偏移量)             *         MAX_COS=cos45             */            exitX = -objectW - getRotationWidthOffset();        } else {            exitX = parentWidth + getRotationWidthOffset();        }        //弹出Card的代码实现        this.frame.animate()                .setDuration(duration)                .setInterpolator(new AccelerateInterpolator())                .x(exitX)                .y(exitY)                .setListener(new AnimatorListenerAdapter() {                    @Override                    public void onAnimationEnd(Animator animation) {                        if (isLeft) {                            //左边移出回调                            mFlingListener.onCardExited();                            mFlingListener.leftExit(dataObject);                        } else {                            //右边移出回调                            mFlingListener.onCardExited();                            mFlingListener.rightExit(dataObject);                        }                        isAnimationRunning = false;                    }                })                .rotation(getExitRotation(isLeft));    }

getExitPoint()

得到离开时的坐标Y的方法

private float getExitPoint(int exitXPoint) {        float[] x = new float[2];        x[0] = objectX;        x[1] = aPosX;        float[] y = new float[2];        y[0] = objectY;        y[1] = aPosY;        LinearRegression regression = new LinearRegression(x, y);        //这个返回值是一个线性方程: y = ax+b        return (float) regression.slope() * exitXPoint + (float) regression.intercept();    }

梳理

走到这里,整体的流程其实还是比较明确的:我们折叠的多个Card的效果是通过类似ListView的实现就行完成的。每一个Card的View在通过设置Listener进行完成Card的左右拖动的效果。而实现弹动的效果是用属性动画完成的。


尾声

希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:

https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp

阅读全文
0 0