自定义view 小圆点随手指滑动

package com.example.abc.myfirstapplication.SelfView;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * 作者:abc on 2016/6/21 14:39
 * 邮箱:liyuchong@kocla.com
 */
public class SimpleView1 extends View implements View.OnClickListener {
    //用于绘制的paint对象
    private Paint mPaint;

    private Rect mBounds;

    //蓝色圆中间显示的数字,每次点击,数字都会增加
    private int mCount;

    //白色圆点(以下称:小圆)的圆心与蓝色圆(以下称:大圆)的圆心的距离
    private float mInnerCircleRadius = 150;

    //定义了大圆圆心和小圆圆心坐标对应的点
    private float center_x, center_y, whitepoint_x, whitepoint_y;
    //小圆
    private Point innerPoint = new Point(whitepoint_x, whitepoint_y);
    //大圆
    private Point centerPoint = new Point(center_x, center_y);

    //定义layout的半径...即大圆的半径
    private int mRadius;

    //定义初始角度...用于onTouchEvent中计算小圆的圆心坐标
    private double StartAngle;


    /*
     * 加速度检测部分
     * 用于检测手指滑动并抬起后,小圆是否需要继续滑动
     * */

    //手指按下和抬起时对应点的角度值
    private float DownAngle;
    private float UpAngle;
    //手指按下时的系统时间
    private long DownTime;
    //每个move动作经过的角度
    private float TmpAngle;
    //判断是否在自由旋转...
    private boolean isFling;
    //每个动作的初始点坐标
    private float mLastX;
    private float mLastY;
    //用于处理后续小圆滑动的线程
    private FlingRunnable mFlingRunnable;

    public SimpleView1(Context context) {
        super(context);
        init();
    }

    public SimpleView1(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

    /**
     * 初始化
     */
    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBounds = new Rect();
        this.setOnTouchListener(onTouchListener);
    }

    /**
     * <p>
     * Measure the view and its content to determine the measured width and the
     * measured height. This method is invoked by {@link #measure(int, int)} and
     * should be overridden by subclasses to provide accurate and efficient
     * measurement of their contents.
     * </p>
     * <p>
     * <p>
     * <strong>CONTRACT:</strong> When overriding this method, you
     * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
     * measured width and height of this view. Failure to do so will trigger an
     * <code>IllegalStateException</code>, thrown by
     * {@link #measure(int, int)}. Calling the superclass'
     * {@link #onMeasure(int, int)} is a valid use.
     * </p>
     * <p>
     * <p>
     * The base class implementation of measure defaults to the background size,
     * unless a larger size is allowed by the MeasureSpec. Subclasses should
     * override {@link #onMeasure(int, int)} to provide better measurements of
     * their content.
     * </p>
     * <p>
     * <p>
     * If this method is overridden, it is the subclass's responsibility to make
     * sure the measured height and width are at least the view's minimum height
     * and width ({@link #getSuggestedMinimumHeight()} and
     * {@link #getSuggestedMinimumWidth()}).
     * </p>
     *
     * @param widthMeasureSpec  horizontal space requirements as imposed by the parent.
     *                          The requirements are encoded with
     *                          {@link MeasureSpec}.
     * @param heightMeasureSpec vertical space requirements as imposed by the parent.
     *                          The requirements are encoded with
     *                          {@link MeasureSpec}.
     * @see #getMeasuredWidth()
     * @see #getMeasuredHeight()
     * @see #setMeasuredDimension(int, int)
     * @see #getSuggestedMinimumHeight()
     * @see #getSuggestedMinimumWidth()
     * @see MeasureSpec#getMode(int)
     * @see MeasureSpec#getSize(int)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //设定该view的宽和高,并设定大小圆的圆心坐标
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//        int withMode = MeasureSpec.getMode(widthMeasureSpec);
//        int withSize = MeasureSpec.getSize(widthMeasureSpec);
        mRadius = getWidth() < getHeight() ? getWidth() / 2 : getHeight() / 2;
        centerPoint.x = getWidth() / 2;
        centerPoint.y = getHeight() / 2;
        innerPoint.x = centerPoint.x + mInnerCircleRadius;
        innerPoint.y = centerPoint.y;

    }


    /**
     * Called from layout when this view should
     * assign a size and position to each of its children.
     * <p/>
     * Derived classes with children should override
     * this method and call layout on each of
     * their children.
     *
     * @param changed This is a new size or position for this view
     * @param left    Left position, relative to parent
     * @param top     Top position, relative to parent
     * @param right   Right position, relative to parent
     * @param bottom  Bottom position, relative to parent
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    /**
     * 绘制该view
     * <p/>
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.BLUE);
        canvas.drawCircle(centerPoint.x, centerPoint.y, mRadius, mPaint);
        mPaint.setColor(Color.WHITE);
        canvas.drawCircle(innerPoint.x, innerPoint.y, 10, mPaint);
//        mPaint.setColor(Color.RED);
//        canvas.drawLine(centerPoint.x - mRadius, centerPoint.y, centerPoint.x + mRadius, centerPoint.y, mPaint);
        mPaint.setColor(Color.YELLOW);
        mPaint.setTextSize(60);
        String text = String.valueOf(mCount);
        mPaint.getTextBounds(text, 0, text.length(), mBounds);
        float textWidth = mBounds.width();
        float textHeight = mBounds.height();
        canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2
                + textHeight / 2, mPaint);
//        StartAngle = getAngle(whitepoint_x, whitepoint_y);
//        Log.d("SimpleView1", "---------------innerPoint.x " + innerPoint.x);
//        Log.d("SimpleView1", "---------------innerPoint.y " + innerPoint.y);
    }

    @Override
    public void onClick(View v) {

    }

    /**
     * Touch动作的监听器
     * 随着手指的滑动,计算出小圆圆心的坐标并不断重绘该view
     */
    private OnTouchListener onTouchListener = new OnTouchListener() {
        //        float oldx, oldy;
        //这个flag用于判断是否发生了点击事件
        boolean flag = true;

        @Override
        public boolean onTouch(View view, MotionEvent event) {


            float x = event.getX();
            float y = event.getY();
            //用于判断该点是否在大圆内部
            if (!isContained(x, y, centerPoint, mRadius)) {
                return true;
            }
            int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    mLastX = x;
                    mLastY = y;
//                    float start1 = getAngle(mLastX, mLastY);
//                    Log.d("SimpleView1", "---------------start1角度是 " + start1);
                    DownAngle = getAngle(x, y, centerPoint);//获取角度...
                    DownTime = System.currentTimeMillis();  //系统的当前时间...
                    TmpAngle = 0;

                    flag = true;
                    //如果当前在进行快速滚动,那么移除对快速移动的回调...其实就是如果这个界面正在旋转,在旋转的期间我DOWN了一下,那么直接就停止旋转...
                    if (isFling) {
                        removeCallbacks(mFlingRunnable);
                        isFling = false;
                        return true;
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    flag = false;
                    float start = getAngle(mLastX, mLastY, centerPoint);
//                    Log.d("SimpleView1", "---------------start角度是 " + start);
                    float end = getAngle(x, y, centerPoint);
//                    Log.d("SimpleView1", "---------------end角度是 " + end);

                    TmpAngle = end - start;
//                    Log.d("SimpleView1", "---------------tmp角度是 " + TmpAngle);
                    //新的角度值,根据这个角度值可以判断现在内部原点应该所在的象限及原点坐标
                    StartAngle += TmpAngle;

                    //更新小圆的圆心坐标并更新StartAngle的值
                    StartAngle = updateInnerCircleOriginalPoint(StartAngle, innerPoint, mInnerCircleRadius, centerPoint);

                    //将初始值设置为旋转后的值...
                    mLastX = x;
                    mLastY = y;
                    break;
                case MotionEvent.ACTION_UP:
                    //获取手指抬起时的角度...
                    UpAngle = getAngle(x, y, centerPoint);
                    //计算每秒钟移动的角度...
                    float anglePrMillionSecond = (UpAngle - DownAngle) * 1000
                            / (System.currentTimeMillis() - DownTime);
                    Log.d("SimpleView1", "---------------anglePrMillionSecond " + anglePrMillionSecond);
                    //如果数值大于这个指定的数值,那么就会认为是加速滚动...
                    if (Math.abs(anglePrMillionSecond) > 230 && !isFling) {
                        //开启一个新的线程,让其进行自由滚动...
                        post(mFlingRunnable = new FlingRunnable(anglePrMillionSecond));
                    }

                    if (Math.abs(anglePrMillionSecond) > 230 || isFling) {
                        return true;
                    }
                    if (flag) {
                        mCount++;
                        invalidate();
                    }
                    flag = true;
                    break;
            }
            return true;
        }
    };

    
/**
 * 该函数根据小圆圆心新的角度值计算圆心的坐标
 * 根据圆心所在角度转换为弧度,计算sin和cos值,再结合大圆圆心坐标计算出小圆圆心坐标。
 *
 * @param newAngle
 * @param point
 * @param innerRadius
 * @param centerPoint
 * @return
 */
private double updateInnerCircleOriginalPoint(double newAngle, Point point, float innerRadius, Point centerPoint) {
    double StartAngle_PI;
    StartAngle_PI = newAngle * Math.PI / 180;
    double y_distance = Math.sin(StartAngle_PI) * innerRadius;
    double x_distance = Math.cos(StartAngle_PI) * innerRadius;
    point.x = (float) (x_distance + centerPoint.x);
    point.y = (float) (centerPoint.y - y_distance);
    invalidate();
    return newAngle;
}
/**
     * 判断给定的x和y所对应的点是否在以centerPoint为圆心以radius为半径的圆内部
     *
     * @param x
     * @param y
     * @param centerPoint
     * @return
     */
    private boolean isContained(float x, float y, Point centerPoint, float radius) {

        Point now = new Point(x, y);
        if (centerPoint.getDistance(now) > radius)
            return false;

        return true;
    }

    /**
     * 自己定义的Point类
     */
    class Point {

        public float getX() {
            return x;
        }

        public void setX(float x) {
            this.x = x;
        }

        public float getY() {
            return y;
        }

        public void setY(float y) {
            this.y = y;
        }

        private float x;
        private float y;

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

        public double getDistance(Point p) {
            double _x = Math.abs(this.x - p.x);
            double _y = Math.abs(this.y - p.y);
            return Math.sqrt(_x * _x + _y * _y);
        }
    }

    /**
     * 计算手指触摸的点在以point为圆心的点到x轴正向的角度
     * 我这里处理的发杂了,其实可以直接返回弧度值,那样在 updateInnerCircleOriginalPoint()方法中就不需要重新计算弧度了
     * @param xTouch
     * @param yTouch
     * @return
     */
    private float getAngle(float xTouch, float yTouch, Point point) {

        float ac = (float) (Math.asin((point.y - yTouch) / Math.hypot(Math.abs(xTouch - point.x), Math.abs(yTouch - point.y))) * 180 / Math.PI);
        if (xTouch >= point.x) {
            return ac;
        } else {
            if (yTouch < point.y)
                return 180 - ac;
            else return -180 - ac;
        }
    }




    private class FlingRunnable implements Runnable {

        private float velocity;

        public FlingRunnable(float velocity) {
            this.velocity = velocity;
        }

        public void run() {
            if ((int) Math.abs(velocity) < 20) {
                isFling = false;
                return;
            }
            //减速旋转...
            isFling = true;
            StartAngle += (velocity / 30);
            velocity /= 1.0666F;
            postDelayed(this, 30);//需要保证时刻对页面进行刷新..因为始终要进行新的布局...
            StartAngle = updateInnerCircleOriginalPoint(StartAngle, innerPoint, mInnerCircleRadius, centerPoint);
//            invalidate();
        }
    }
}

注释挺清楚的,如果有什么疑问,还请多多在评论中提出!

补充一个自定义ViewGroup的参考链接:http://www.cnblogs.com/RGogoing/p/4676654.html?utm_source=tuicool&utm_medium=referral









  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值