从头开始敲代码之《从BaseApplication/Activity开始(五)》(自定义控件,实现点击/滑动翻页)

转载请注明出处:王亟亟的大牛之路

开场白惯用鼓励诗句:

黑发不知勤学早,白首方悔读书迟。 —— 颜真卿《劝学诗》

这一系列的博文这是第五篇了,感谢大家的支持以及陪伴,往后我也会继续努力写出高质量的内容,谢谢

今天上的是一个自定义View,新鲜出炉,先上下效果(是一张张截图拼接的Gif动画都看不出来了,大家理解就行可以下Demo跑)

样例分析(最简单的描述了)

黑色线条是我们的手机

红色是我们自定义的”TitleBar”

蓝色是我们的自定义布局

紫色是自定义布局填充的内容

我们只需要配置我们蓝色内容的参数就可以对动画效果以及大小等进行设置。

PS:因为 蓝色内容吃掉了所有蓝色区域的OnTouch,所以紫色就不要做用户交互内容了,纯粹做展示吧TOT(小的该死)

看下项目结构:

就比上次的代码多了一些资源文件和4个类,一个就是我们的麦麦Activity,另外3个解释下

DraggableFlipView我们的自定义控件

DragGestureDetector我们的触碰效果处理类

FlipListener动作展示以及处理结果

OK,开始分析

DraggableFlipView

public class DraggableFlipView extends FrameLayout implements DragGestureDetector.DragGestureListener {
    //一系列的声明,不一一解释了,后面用到了会加以解释

    private static final float DRAG_THRESHOLD_PARAM = 50.0f;
    private static final int DEFAULT_VALUE = 0;
    private static final int DEFAULT_DRAGGABLE_VALUE = 50;
    private static final int DEFAULT_DRAG_DETECT_VALUE = 7;

    private DragGestureDetector mDragGestureDetector;
    private boolean isAnimation;
    private boolean isDragging;
    private int mAngle;
    private int mDraggableAngle;
    private int mDragDetectAngle;
    private boolean mIsReverse;
    private FlipListener mFlipListener;

    private RelativeLayout mFrontLayout;
    private RelativeLayout mBackLayout;

    //声明左右状态的枚举
    private enum RotateDirection {
        RIGHT(1), LEFT(-1);

        private int mValue;

        RotateDirection(int value) {
            this.mValue = value;
        }

        public int getValue() {
            return mValue;
        }
    }

    //构造函数
    public DraggableFlipView(Context context) {
        this(context, null);
    }

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

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

    //初始化参数
    private void init(Context context, AttributeSet attrs) {
        //获取布局对象并加以填充,默认显示mBackLayout这个布局
        mFrontLayout = new RelativeLayout(context);
        RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
        mFrontLayout.setLayoutParams(params1);

        mBackLayout = new RelativeLayout(context);
        RelativeLayout.LayoutParams params2 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
        mBackLayout.setLayoutParams(params2);

        this.addView(mFrontLayout);
        this.addView(mBackLayout);
        mBackLayout.setVisibility(View.INVISIBLE);

        //初始化FlipListener,传入2个布局,第二个参数为不显示的布局
        mFlipListener = new FlipListener(mFrontLayout, mBackLayout, this);
        mDragGestureDetector = new DragGestureDetector(this);

        //获取 标签
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DraggableFlipView);
        LayoutInflater.from(context).inflate(a.getResourceId(R.styleable.DraggableFlipView_frontView, DEFAULT_VALUE), mFrontLayout);
        LayoutInflater.from(context).inflate(a.getResourceId(R.styleable.DraggableFlipView_backView, DEFAULT_VALUE), mBackLayout);
        //填充标签数据
        mDraggableAngle = a.getInteger(R.styleable.DraggableFlipView_draggableAngle, DEFAULT_DRAGGABLE_VALUE);
        mDragDetectAngle = a.getInteger(R.styleable.DraggableFlipView_dragDetectAngle, DEFAULT_DRAG_DETECT_VALUE);
    }
    //onInterceptTouchEvent这个事件是从父控件开始往子控件传的,直到有拦截或者到没有这个事件的view,并且使用  mDragGestureDetector.setPointMap(ev);进行参数的传递
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mDragGestureDetector == null) return false;
        int action = ev.getAction() & MotionEvent.ACTION_MASK;
        switch (action) {
            case MotionEvent.ACTION_UP:
                break;
            case MotionEvent.ACTION_MOVE:
                if (Math.abs(ev.getX() - mDragGestureDetector.getTouchPoint().getX())
                        > DRAG_THRESHOLD_PARAM
                        || Math.abs(ev.getY() - mDragGestureDetector.getTouchPoint().getY())
                        > DRAG_THRESHOLD_PARAM) {
                    mDragGestureDetector.setPointMap(ev);
                    return true;
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                return true;
        }
        return false;
    }
    //onTouch这个事件是从子控件回传到父控件的,一层层向下传
    //mDragGestureDetector.onTouchEvent(event)来实现onTouchEvent的操作
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mDragGestureDetector != null) {
            mDragGestureDetector.onTouchEvent(event);
        }
        return true;
    }

    //计算,并判断以哪种方式来实现切换动画
    @Override
    public void onDragGestureListener(DragGestureDetector dragGestureDetector, int action) {
        if (isAnimation) return;
        if (action == MotionEvent.ACTION_UP) {
            if (mAngle >= mDragDetectAngle) {
                startAutoRotateAnimation(RotateDirection.RIGHT);
            } else if (mAngle < -mDragDetectAngle) {
                startAutoRotateAnimation(RotateDirection.LEFT);
            }
            return;
        }

        mAngle = (dragGestureDetector.deltaX - dragGestureDetector.prevDeltaX) > 0 ? ++mAngle : --mAngle;
        if (Math.abs(mAngle) > mDragDetectAngle) isDragging = true;
        if(isDragging) this.setRotationY(mAngle);

        if (mAngle >= mDraggableAngle) {
            startAutoRotateAnimation(RotateDirection.RIGHT);
        } else if (mAngle < -mDraggableAngle) {
            startAutoRotateAnimation(RotateDirection.LEFT);
        }
    }

    private void startAutoRotateAnimation(RotateDirection rotateDirection) {
        isAnimation = true;
        if (mIsReverse) {
            mFlipListener.reverse();
        } else {
            mIsReverse = true;
        }

        mFlipListener.setRotateDirection(rotateDirection.getValue());
        //动画的平滑过渡 可以参照 http://blog.csdn.net/guolin_blog/article/details/43536355
        //讲的很详细
        ValueAnimator mFlipAnimator = ValueAnimator.ofFloat(0f, 1f);
        mFlipAnimator.addUpdateListener(mFlipListener);
        mFlipAnimator.start();
        mFlipAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mAngle = 0;
                isAnimation = false;
                isDragging = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
    }
}

DragGestureDetector

public class DragGestureDetector {

    public float deltaX;
    public float deltaY;
    public float prevDeltaX;
    public float prevDeltaY;
    public int originalIndex;
    public float velocityX;
    public float velocityY;

    //储存用户操作路径
    private HashMap<Integer, TouchPoint> pointMap = new HashMap<>();

    private DragGestureListener dragGestureListener;

    //构造函数
    public DragGestureDetector(DragGestureListener dragGestureListener) {
        this.dragGestureListener = dragGestureListener;
        //初始化坐标
        pointMap.put(0, createPoint(0.f, 0.f));
    }

    //储存坐标点
    public void setPointMap(MotionEvent event) {
        float eventX = event.getX();
        float eventY = event.getY();
        TouchPoint downPoint = pointMap.get(0);
        if (downPoint != null) {
            downPoint.setXY(eventX, eventY);
            return;
        }
        downPoint = createPoint(eventX, eventY);
        pointMap.put(0, downPoint);
    }

    //获取坐标点
    public TouchPoint getTouchPoint() {
        return pointMap.get(originalIndex);
    }

    //用户触控坐标点相应的计算
    synchronized public boolean onTouchEvent(MotionEvent event) {

        float eventX = event.getX(originalIndex);
        float eventY = event.getY(originalIndex);

        int action = event.getAction() & MotionEvent.ACTION_MASK;
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                TouchPoint originalPoint = pointMap.get(originalIndex);
                if (originalPoint != null) {
                    deltaX = eventX - originalPoint.x;
                    deltaY = eventY - originalPoint.y;

                    if (dragGestureListener != null) {
                        dragGestureListener.onDragGestureListener(this, action);
                    }

                    velocityX = deltaX - prevDeltaX;
                    velocityY = deltaY - prevDeltaY;
                    prevDeltaX = deltaX;
                    prevDeltaY = deltaY;
                }
                break;
            }

            case MotionEvent.ACTION_UP: {
                TouchPoint originalPoint = pointMap.get(originalIndex);
                if (originalPoint != null && dragGestureListener != null) {
                    dragGestureListener.onDragGestureListener(this, action);
                }
                velocityX = velocityY = 0;
                prevDeltaX = prevDeltaY = 0;
                deltaX = deltaY = 0;
                break;
            }
            default:
        }
        return false;
    }

    private TouchPoint createPoint(float x, float y) {
        return new TouchPoint(x, y);
    }

    public interface DragGestureListener {
        void onDragGestureListener(DragGestureDetector dragGestureDetector, int action);
    }

    //坐标类
    public class TouchPoint {

        private float x;
        private float y;

        public TouchPoint(float x, float y) {
            this.x = x;
            this.y = y;
        }

        public TouchPoint setXY(float x, float y) {
            this.x = x;
            this.y = y;
            return this;
        }

        public float getX() {
            return this.x;
        }

        public float getY() {
            return this.y;
        }
    }
}

FlipListener

public class FlipListener implements ValueAnimator.AnimatorUpdateListener {

    private View mParentView;
    private View mFrontView;
    private View mBackView;
    private boolean mFlipped;
    private int mDirection;

    //构造函数
    public FlipListener(final View front, final View back, final View parent) {
        this.mParentView = parent;
        this.mFrontView = front;
        this.mBackView = back;
        this.mBackView.setVisibility(View.GONE);
    }

    @Override
    public void onAnimationUpdate(final ValueAnimator animation) {
        final float value = animation.getAnimatedFraction();
        final float scaleValue = 0.625f + (1.5f * (value - 0.5f) * (value - 0.5f));

        //根据传入的mDirection(1或者-1)进行计算并且逻辑判断
        if (value <= 0.5f) {
            this.mParentView.setRotationY(180 * value * mDirection);
            if (mFlipped) setStateFlipped(false);
        } else {
            this.mParentView.setRotationY(-180 * (1 - value) * mDirection);
            if (!mFlipped) setStateFlipped(true);
        }
        this.mParentView.setScaleX(scaleValue);
        this.mParentView.setScaleY(scaleValue);
    }

    //初始化自定义View时调用
    public void reverse() {
        View temp = mBackView;
        mBackView = mFrontView;
        mFrontView = temp;
    }

    public void setRotateDirection(int direction) {
        mDirection = direction;
    }

    //具体切换试图
    private void setStateFlipped(boolean flipped) {
        mFlipped = flipped;
        this.mFrontView.setVisibility(flipped ? View.GONE : View.VISIBLE);
        this.mBackView.setVisibility(flipped ? View.VISIBLE : View.GONE);
    }
}

OK!!!!实现就这些啦

源码:http://yunpan.cn/cHwL97TfNdApF 访问密码 d11f

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-29 00:31:51

从头开始敲代码之《从BaseApplication/Activity开始(五)》(自定义控件,实现点击/滑动翻页)的相关文章

从头开始敲代码之《从BaseApplication/Activity开始》

转载请注明出处王亟亟的大牛之路 其安易持,其未兆易谋:其脆易泮,其微易散.为之于未有,治之于未乱.合抱之木,生于毫末:九层之台,起于垒土:千里之行,始于足下.为者败之,执者失之.是以圣人无为故无败,无执故无失.民之从事,常于几成而败之.慎终如始,则无败事.是以圣人欲不欲,不贵难得之货,学不学,复众人之所过,以辅万物之自然而不敢为. 作为系列专题的第一篇,这一篇文章属于小难产,中间夹杂着一些工作上的事,一些蛋疼的事(学车之类的),说实在的,做了Coder之后发现业余时间还真不是太多....唉...

从头开始敲代码之《从BaseApplication/Activity开始(二)》

转载请注明出处:王亟亟的大牛之路 愿意花时间写东西不容易,人啊,都是有血有肉有思想的,借鉴是学习,纯Copy就不好了,谢谢 部分资料参考于网上. <赠梁任父同年>黄遵宪 寸寸河山寸寸金,侉离分裂力谁任? 杜鹃再拜忧天泪,精卫无穷填海心. 上一篇我们讲到了简易的封装 对我们提高效率的好处,这一篇继续写下去,如果第一篇没看过的希望能看下,方便理解.链接:亟亟在安卓的进阶实例 这一次我们利用最基本的Activity生命周期中的方法,来对用户蓝牙进行识别操作,并在过程中考虑用户层面的操作理解,顺便补充

从头开始敲代码之《从BaseApplication/Activity开始(四)》

转载请注明出处:王亟亟的大牛之路 早上无聊看以前下的一大堆资料,发现一个用JNI实现的模糊效果,效果都差不多,但是对JNI的不熟悉让我不太推荐这种办法(不了解的总不方便,调试,修改都是) 然后在Git上找到个不错的实现,还是分2种的,应对于各种需要. 这一篇文章会介绍什么 1.模糊视图处理 2.线程操作优化 1.Renderscript 2.FastBlur 效果图 布局: <?xml version="1.0" encoding="utf-8"?> &

每天下午5点半下班,但是我很少在晚上1点之前睡过觉。因为都在敲代码

有时候,并不是我不想睡觉,不想休息,我也想舒适,也很想舒舒服服的坐着不工作,但很难做到,一天不敲代码,一天不学习新知识现在就浑身难受,不知道我这股热情能坚持多久,但这样的强度已经连续坚持了7个多月了,我很充实也很充实,每天我的精神饱满,战斗力极强.在做项目的时候,有一些细节方面的东西,我总想做到最好,总想做到用户体验最优,代码封装最优.有时候睡觉的时候也在想.我对未来是充满信心和期待的,我并不着急,我还很年轻,永远都年轻~ 前几天,小杰来公司面试,被我公司面试官虐的不要不要的,我总以为他很厉害,

使用Alcatraz为Xcode安装XActivatePowerMode插件, 从此敲代码逼格大大滴~

Alcatraz 是一款 Xcode的插件管理工具,可以用来管理XCode的 插件.模版以及颜色配置的工具. 关于Alcatraz的安装,这里有一篇不错的博文,请参考安装:http://www.cnblogs.com/wendingding/p/4964661.html 最近有一款插件叫ActivatePowerMode,堪称装逼神器啊,敲代码的时候,炫彩的火花尾巴效果简直屌炸天~,详情可以看XActivatePowerMode在github中的介绍https://github.com/qfis

程序员不能只会敲代码还要会投资理财

程序员不能只会敲代码,还要会理财或者说投资. 当我们步入职场,随着我们工作经验的增长我们的薪资相应的也会快速提高,很多人可能思维上还没有改变过来,不知道如何利用自己的闲钱去保值或者增值.当然现在互联网理财发展已经比较的成熟了,大多数的人也知道用自己发的工资直接购买余额宝来赚取利息,余额宝可以说是开启了国人理财的意识.对于我们普通人来说可以有以下几种投资: XX宝比如余额宝(货币基金),收益比银行活期存款或者某些定期存款还高,风险低几乎不会损失你的本金,但是一年的收益还是不能抵抗通货膨胀: 互联网

iOS Sprite Kit教程之编敲代码以及Xcode的介绍

iOS Sprite Kit教程之编敲代码以及Xcode的介绍 Xcode界面介绍 一个Xcode项目由非常多的文件组成,比如代码文件.资源文件等.Xcode会帮助开发人员对这些文件进行管理.所以,Xcode的界面也比較复杂,如图1.40所看到的. watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" > 图

(016)给定一个有序数组(递增),敲代码构建一棵具有最小高度的二叉树(keep it up)

给定一个有序数组(递增),敲代码构建一棵具有最小高度的二叉树. 因为数组是递增有序的.每次都在中间创建结点,类似二分查找的方法来间最小树. struct TreeNode { int data; TreeNode* leftChild; TreeNode* rightChild; }; void newNode(TreeNode*& vNode, int vData) { vNode = new TreeNode; vNode->data = vData; vNode->leftChi

十年码农,过了十年他们依旧在敲代码

摘要:话说程序员也是一个吃青春饭的职业,经常需要加班.高强度工作.新技术学习需求等等,让青春不再来的从业者感觉吃力,但仍然有一大批人因为各种原因十年如一日的敲着代码,十年历程是怎样的一种经历,你会成为其中之一吗? 十年前的2004年,中国网民突破9000万可喜可贺,第三代互动式搜索引擎搜狗刚刚问世,新浪.搜狐.网易是中国顶级的互联网企业,2004互联网大事记里看不到BAT的影子,小编在读初中,当然,也有一批很平凡的程序员在敲代码. 来看看这十几位码农十年或平凡.或漂泊的历程(以下程序员信息主要来