zoukankan      html  css  js  c++  java
  • 刷新头的升级版,仿QQ的红色消息小球

    上一篇博客写了水滴状的下拉刷新头,不过那个只能垂直下拉,也就是一个方向,而且经常用QQ的人也知道,QQ消息来了后,列表右边会有一个红色消息小球,这个小球是可以拉动的,拉起来就像一根皮筋一样,效果很棒,于是我根据那个上篇博客,进行修改,得到一个可以平面拉动的小球,基本近似QQ。



    嗯,效果还不错,基本原理和上篇一样,不过因为可以平面拖动,和原来相比,就相当于一维到二维进步,计算难度和运算量基本上了几个数量级,还总是要考虑圆心连线的方向性,头都晕了,数学不好可以绕过了偷笑




    连接两个圆的圆心,过一个圆的圆心做垂线,交圆上AB两点,同理在大圆上面也作出CD两点

    分别连线,现在要根据两个圆的圆心坐标和两个圆的半径求出ABCD这4点坐标


    一开始想用解析几何的方式求4点坐标,做法:

    求出圆心连线的斜率,根据垂直直线斜率相乘等于-1,得到AB直线的斜率

    AB直线过原点,则AB直线方程可以用点斜式表达出,与圆的方程联解,即可得到AB两点

    问题有两个:1,需要考虑到斜率不存在的情况2,需要求解二元二次方程组,很麻烦


    于是,想到了三角函数的办法,因为计算机中求取反三角很简单,所以可以完全不顾解析几何那一套,做法:

    求出圆心连线的斜率,根据垂直直线斜率相乘等于-1,得到AB直线的斜率

    根据斜率k=tanθ,θ是直线AB和X轴的夹角,θ=arctank

    AB点都是圆上的点,圆的方程已经说的很清楚了,x=cosθ*R+Rx,y=sinθ*R+Ry,其中Rx,Ry都是圆心坐标

    CD点同理可得,下面需要根据这四个点做二次曲线,因此需要借助到中间点E,F。

    直线EF是圆心连线的垂直平分线,交A,B两点延生的两条平行于圆心连线于E,F

    因此这两个点也可以通过三角函数来求,以A为原点,已知AF的距离(圆心距一半),AF和X轴的夹角(θ+90),很容易求得

    于是乎,剩下两个半圆弧的绘制方法,依旧是用那个方法、arcTo,依旧是要注意圆弧的方向,和整条路径的方向,不然很可能无法闭合曲线。


    当然,实际情况比这还要复杂的多

    你需要考虑这种情况


    当圆出现在下面时,AB直线的斜率依旧不变,可是绘制圆弧的方向却完全不一样了,因此需要在代码中加入一个判断

    闭合曲线也是麻烦的要死,嗯,说到这里,绘制这部分算是完了


    至于触摸事件,摸着改动大圆圆心坐标就是了,然后刷新view


    说说回弹,这次用的回弹和上次有所不同,上次用的是匀速返回,这种效果并不是很好

    想作出那种皮筋,或者弹簧的效果来,果然,想到弹簧上的小球正好就是物理上面所说的简谐运动

    这正是我想要的,于是乎,看原理:

    我需要的是小球运动时圆心距的改变,从圆心距推算出大圆的圆心坐标

    而我们可以让圆心距改变的大小是一个简谐运动,圆心距x=A*sin(w*t+Ψ

    振幅A不停减少,一直到0停止,t的经历的时间(不是间隔时间)
    根据圆心距和角度angle,可及时的计算出运动中的圆心坐标
    small球在上面时,即islow=1,x为正,在下面时islow=-1,x为负,需要根据这个计算初相φ,φ=islow*π/2
    A=手放开的时候的圆心距
    周期T=2π/ω,则ω=2π/T

    当A递减到0,回弹线程结束


    好了,下面贴代码:

    package com.example.kaifa.myapplication;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.graphics.RectF;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.View;
    
    /**
     * TODO: document your custom view class.
     */
    public class DanqiuView extends View {
    
    
        private Paint mPaint;
        /**
         * 拉伸进度
         */
        private float progress = 0;
        /**
         * view的宽高
         */
        private int viewheight, viewwdith;
    
        /**
         * 大圆半径
         */
        private float GreatCircleRadius = 20;
        /**
         * 小圆半径
         */
        private float SmallCircleRadius = 20;
        /**
         * 大圆的圆心
         */
        private Point GreatCirclePoint;
        /**
         * 小圆的圆心
         */
        private Point SmallCirclePoint;
        /**
         * 分别在大圆和小圆上面的4个点
         */
        private Point A, B, C, D;
        /**
         * AB直线与X轴的夹角(AB直线和圆心连线垂直),单位 弧度
         */
        private double angle;
        /**
         * 手指按下接触到的第一个点
         */
        private Point firstPoint;
        /**
         * 两条二次曲线的分别两个中间点
         */
        private Point ACmiddlePoint, BDmiddlePoint;
        /**
         * 绘制路径
         */
        private Path mPath;
        /**
         * 1表示小圆在大圆下面,-1反之
         */
        private int islow = -1;
        /**
         * 两园的圆心距
         */
        double dance=0;
        /**
         * 周期,单位/毫秒
         */
        float T=2000;
    
        public DanqiuView(Context context) {
            super(context);
            init(null, 0);
        }
    
        public DanqiuView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(attrs, 0);
        }
    
        public DanqiuView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init(attrs, defStyle);
        }
    
        private void init(AttributeSet attrs, int defStyle) {
            // Load attributes
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setColor(0xff0000ff);
            mPaint.setStyle(Paint.Style.FILL);
            mPath = new Path();
    
            GreatCirclePoint = new Point();
            SmallCirclePoint = new Point();
            A = new Point();
            B = new Point();
            C = new Point();
            D = new Point();
            firstPoint = new Point();
    
            ACmiddlePoint = new Point();
            BDmiddlePoint = new Point();
    
    
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            viewwdith = w;
            viewheight = h;
    
            GreatCirclePoint.x = w / 2;
            GreatCirclePoint.y = h / 2;
    
            SmallCirclePoint.x = GreatCirclePoint.x;
            SmallCirclePoint.y = GreatCirclePoint.y;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //路径重置
            mPath.reset();
            //若重合,则绘制初始圆
            if (GreatCirclePoint.x == SmallCirclePoint.x && GreatCirclePoint.y == SmallCirclePoint.y) {
                //初始绘制
                mPath.addCircle(GreatCirclePoint.x, GreatCirclePoint.y, GreatCircleRadius, Path.Direction.CW);
                canvas.drawPath(mPath, mPaint);
                return;
            }
            //计算四个点的坐标和角度值
            //AB直线和圆心连线垂直,斜率相乘等于-1.因此可以得到AB直线的斜率
            float kAB = (GreatCirclePoint.x - SmallCirclePoint.x) / (SmallCirclePoint.y - GreatCirclePoint.y);
            angle = Math.atan(kAB);
            Log.v("xingyun", "斜率K=" + kAB + "  角度a=" + angle);
            A.x = (float) (GreatCirclePoint.x + GreatCircleRadius * Math.cos(angle));
            A.y = (float) (GreatCirclePoint.y + GreatCircleRadius * Math.sin(angle));
    
            B.x = (float) (GreatCirclePoint.x + GreatCircleRadius * Math.cos(angle + Math.PI));
            B.y = (float) (GreatCirclePoint.y + GreatCircleRadius * Math.sin(angle + Math.PI));
    
            C.x = (float) (SmallCirclePoint.x + SmallCircleRadius * Math.cos(angle));
            C.y = (float) (SmallCirclePoint.y + SmallCircleRadius * Math.sin(angle));
    
            D.x = (float) (SmallCirclePoint.x + SmallCircleRadius * Math.cos(angle + Math.PI));
            D.y = (float) (SmallCirclePoint.y + SmallCircleRadius * Math.sin(angle + Math.PI));
    
            if (GreatCirclePoint.y < SmallCirclePoint.y) {
                islow = 1;
            } else {
                islow = -1;
            }
    
            dance = Math.sqrt((GreatCirclePoint.x - SmallCirclePoint.x) * (GreatCirclePoint.x - SmallCirclePoint.x) + (GreatCirclePoint.y - SmallCirclePoint.y) * (GreatCirclePoint.y - SmallCirclePoint.y)) / 2;
    
            progress = (float) dance * 2 / viewheight;
            SmallCircleRadius = (float) (20 * (1 - progress * 0.1));
    
            GreatCircleRadius = (float) (20 * (1 - progress));
    
            if (progress < 0.5) {
                BDmiddlePoint.x = (float) (B.x + dance * Math.cos(angle + islow * Math.PI / 2));
                BDmiddlePoint.y = (float) (B.y + dance * Math.sin(angle + islow * Math.PI / 2));
    
                ACmiddlePoint.x = (float) (A.x + dance * Math.cos(angle + islow * Math.PI / 2));
                ACmiddlePoint.y = (float) (A.y + dance * Math.sin(angle + islow * Math.PI / 2));
            } else {
                BDmiddlePoint.x = GreatCirclePoint.x / 2 + SmallCirclePoint.x / 2;
                BDmiddlePoint.y = GreatCirclePoint.y / 2 + SmallCirclePoint.y / 2;
                ACmiddlePoint.x = BDmiddlePoint.x;
                ACmiddlePoint.y=BDmiddlePoint.y;
            }
            mPath.arcTo(new RectF(GreatCirclePoint.x - GreatCircleRadius, GreatCirclePoint.y - GreatCircleRadius
                            , GreatCirclePoint.x + GreatCircleRadius, GreatCirclePoint.y + GreatCircleRadius),
                    (float) (angle * 180 / Math.PI), islow * (-180));
            //从B点到D点,选取其中心点作为渐进点
            mPath.quadTo(BDmiddlePoint.x, BDmiddlePoint.y, D.x, D.y);
    
            mPath.arcTo(new RectF(SmallCirclePoint.x - SmallCircleRadius, SmallCirclePoint.y - SmallCircleRadius
                            , SmallCirclePoint.x + SmallCircleRadius, SmallCirclePoint.y + SmallCircleRadius),
                    (float) (angle * 180 / Math.PI+180), islow * (-180));
    
            //从A点到C点
            mPath.quadTo(ACmiddlePoint.x, ACmiddlePoint.y, A.x, A.y);
    
            canvas.drawPath(mPath, mPaint);
    
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
    
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    firstPoint.x = event.getX();
                    firstPoint.y = event.getY();
    
                    break;
                case MotionEvent.ACTION_MOVE:
                    float dx = event.getX() - firstPoint.x;
                    float dy = event.getY() - firstPoint.y;
    
                    if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {
                        SmallCirclePoint.x = SmallCirclePoint.x + dx;
                        SmallCirclePoint.y = SmallCirclePoint.y + dy;
                        invalidate();
                    }
    
                    firstPoint.x = event.getX();
                    firstPoint.y = event.getY();
    
                    break;
    
                case MotionEvent.ACTION_UP:
                    //开启线程匀速返回,回弹
                    new MyTread().start();
                    break;
    
            }
            return true;
        }
    
    
    
        /**
         * 回弹的线程,作为简谐运动,则圆心距x=Asin(ωt+φ),振幅A不停减少,一直到0停止,t的经历的时间(不是间隔时间)
         * 根据圆心距和角度angle,可及时的计算出运动中的圆心坐标
         * small球在上面时,即islow=1,x为正,在下面时islow=-1,x为负,需要根据这个计算初相φ,φ=islow*π/2
         * A=手放开的时候的圆心距
         * 周期T=2π/ω,则ω=2π/T
         */
        class MyTread extends Thread {
            @Override
            public void run() {
    
                float A=(float)dance*2;
                float fai=(float)( islow*Math.PI/2);
                float w=(float)(2* Math.PI/T);
                long t=0;
                double angle1=angle;
    
                while (A>0){
                    float x=(float)(A*Math.sin(w*t+fai));
                    A=A-(float)0.5;
                    t=t+10;
                    Log.v("xingyun","振幅:"+A+"  X:"+x);
                    SmallCirclePoint.x=GreatCirclePoint.x+x*(float)Math.cos(angle1+Math.PI/2);
                    SmallCirclePoint.y=GreatCirclePoint.y+x*(float) Math.sin(angle1+Math.PI/2);
    
                    try {
                        sleep(10);
                        postInvalidate();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
    
            }
        }
    
    
    
        private class Point {
            float x, y;
        }
    
    
    }
    


    能力有限,只能做到这里啦~(计算量这么大幸好也没出现卡顿的现象,运气好好~)

    完~

  • 相关阅读:
    网络管理和nmcli命令的使用——网络接口配置-bonding实验步骤
    raid组合优缺点介绍和创建LVM实验个人笔记
    磁盘分区就是这么简单,电脑小白都能看懂的磁盘分区教程!
    C盘优化之桌面移动法,拯救你爆满的C盘!
    电脑软件打开也有讲究,电脑软件打开方式总结!
    电脑使用建议大全,注意这些细节可以让你的电脑更好用!
    CentOS服务器apache绑定多个域名的方法
    CentOS 7使用yum安装PHP5.6
    PhpMyAdmin 配置文件现在需要一个短语密码的解决方法
    CentOs 7.*中配置安装phpMyAdmin的完整步骤记录
  • 原文地址:https://www.cnblogs.com/xingyun1992/p/5021086.html
Copyright © 2011-2022 走看看