Android 很有意思的控件:粘性控件

一、效果如图:

示例图:
Android 很有意思的控件:粘性控件_第1张图片
应用程序图:
Android 很有意思的控件:粘性控件_第2张图片

———————————————————————
有需求者请加qq:136137465,非诚勿扰
(java 架构师全套教程,共760G, 让你从零到架构师,每月轻松拿3万)
01.高级架构师四十二个阶段高
02.Java高级系统培训架构课程148课时
03.Java高级互联网架构师课程
04.Java互联网架构Netty、Nio、Mina等-视频教程
05.Java高级架构设计2016整理-视频教程
06.架构师基础、高级片
07.Java架构师必修linux运维系列课程
08.Java高级系统培训架构课程116课时
(送:hadoop系列教程,java设计模式与数据结构, Spring Cloud微服务, SpringBoot入门)
——————————————————————–

二、分析:

1、应用的地方:如未读数据的清除等
2、这个控件要实现哪些功能呢?
1)拖拽超出范围时,断开了,此时我们松手,图标消失
2)拖拽超出范围时,断开了,此时我们把图标移动回去,图标恢复原样
3)拖拽没有超出范围时,此时我们松手,图标弹回去

3、如何实现:
1)我们先画个两个静态的圆圈,一个大的,一个小的
Android 很有意思的控件:粘性控件_第3张图片
2)绘制中间连接的部分:
Android 很有意思的控件:粘性控件_第4张图片
3)把静态的数值变成变量
4)不断地修改变量,重绘界面,就能动起来

三、实现简单的

1、创建自定义view

/**
 * @描述 粘性控件
 * @项目名称 App_imooc
 * @包名 com.android.imooc.goo
 * @类名 GooView
 * @author chenlin
 * @date 2015年6月2日 下午8:40:53
 * @version 1.0
 */

public class GooView extends View {

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

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

    public GooView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init();
    }

    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.RED);
    }

    @Override
    protected void onDraw(Canvas canvas) {
    }

}

2、在onDraw里绘制个圆

private Paint mPaint;
private float mCircleCenter = 150f;
private float mRadius = 14f;//直径

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
    canvas.drawCircle(mCircleCenter, mCircleCenter, mRadius, mPaint);
}

3、生成主页

/**
 * @描述         主页
 * @项目名称      App_imooc
 * @包名         com.android.imooc.goo
 * @类名         GooActivity
 * @author      chenlin
 * @date        2015年6月2日 下午8:57:16
 * @version     1.0
 */

public class GooActivity extends Activity {
    private GooView mGooView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        mGooView = new GooView(this);
        setContentView(mGooView);
    }
}

4、开始画拖拽的大圆

private float mMoveCircleCenter = 70f;
private float mMoveRadius = 20f;//直径
canvas.drawCircle(mMoveCircleCenter, mMoveCircleCenter, mMoveRadius, mPaint);

5、画拖拽的填充部分,这是重点,绘制使用drawPath

canvas.drawPath(path, mPaint);

但如何才能实现有弯曲的填充物呢?使用了path,
只要一个控制点,如图:
Android 很有意思的控件:粘性控件_第5张图片
有两个控制点:如图:
Android 很有意思的控件:粘性控件_第6张图片

在这里,我们先绘制一个这样的图,
p1到p2绘制一条贝塞尔曲线
p2到p3绘制直线
p3到p4绘制贝塞尔曲线
p4到p1绘制直线
如图:
Android 很有意思的控件:粘性控件_第7张图片

代码实现:
// 画连接部分

Path path = new Path();
// 50, 250 p2
path.moveTo(250f, 250f);
// 150f, 300f填充物的中间点
// 50f, 250f p1
path.quadTo(150f, 300f, 50f, 250f);
// p3
path.lineTo(50f, 350f);
// p4
path.quadTo(150f, 300f, 250f, 350f);
//关闭后,会回到最开始的地方,形成封闭的图形
path.close();

canvas.drawPath(path, mPaint);

Android 很有意思的控件:粘性控件_第8张图片

四、把静态点动态化

好了,现在我们会基本的绘制了,开始把这些点转为动态的吧!
如图:
Android 很有意思的控件:粘性控件_第9张图片

1、把上的圆的点全部转化成float类型的点

PointF staticPointF = new PointF(mStaicCircleCenter, mStaicCircleCenter);
// 画小圆
canvas.drawCircle(staticPointF.x, staticPointF.y, mStaicRadius, mPaint);

PointF movewPointF = new PointF(mMoveCircleCenter, mMoveCircleCenter);
// 画移动的大圆
canvas.drawCircle(movewPointF.x, movewPointF.y, mMoveRadius, mPaint);

2、转化Path上面的点
我们分别使用两个集合存储静态的点与动态点

//存储静态的两个点
PointF[] staticPointFs = new PointF[] { new PointF(250f, 250f), new PointF(250f, 350f) };
//存储移动的两个点
PointF[] moviePointFs = new PointF[] { new PointF(50f, 250f), new PointF(50f, 350f) };
//控制点
PointF controlPointF = new PointF(150f, 300f);

// 画连接部分
Path path = new Path();
// 50, 250 p2
path.moveTo(staticPointFs[0].x, staticPointFs[0].y);
// 150f, 300f填充物的中间点
// 50f, 250f p1
path.quadTo(controlPointF.x, controlPointF.y, moviePointFs[0].x, moviePointFs[0].y);
// p3
path.lineTo(moviePointFs[1].x, moviePointFs[1].y);
// p4
path.quadTo(controlPointF.x, controlPointF.y, staticPointFs[1].x, staticPointFs[1].y);
// 关闭后,会回到最开始的地方,形成封闭的图形
path.close();

canvas.drawPath(path, mPaint);

如图:
Android 很有意思的控件:粘性控件_第10张图片

3、接下来我们就是要把下面的曲型移动到上面两个圆圈之间,形成一个整体
在这里有,两个圆圈的中心点的坐标是已知的,半径也是知道的,现在就剩下求4个点的坐标了
第一个移动点的坐标如何求,看图
Android 很有意思的控件:粘性控件_第11张图片
movePointfs[0].x = movePointf.x + mMoveRadius. cos(a);
movePointfs[0].y = movePointf.y - mMoveRadius. sin(a);

4、开始求得各个点

// 1、获得偏移量
float yOffset = mStaticCenter.y - mMovewCenter.y;
float xOffset = mStaticCenter.x - mMovewCenter.x;
// 2、有了偏移量就可以求出两点斜率了
Double lineK = 0.0;
if (xOffset != 0f) {
    lineK = (double) (yOffset / xOffset);
}
// 3、通过工具求得两个点的集合
mMovewPointFs = GeometryUtil.getIntersectionPoints(mMovewCenter, mMoveRadius, lineK);
mStaticPointFs = GeometryUtil.getIntersectionPoints(mStaticCenter, mStaicRadius, lineK);
// 4、通过公式求得控制点
mControlPointF = GeometryUtil.getMiddlePoint(mStaticCenter, mMovewCenter);

5、得到如图效果:
Android 很有意思的控件:粘性控件_第12张图片

五、开始实现拖动

1、复写onTouchEvent事件,记得在view里要返回true;

@Override
    public boolean onTouchEvent(MotionEvent event) {
        float downX = 0.0f;
        float downY = 0.0f;

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 得到按下的坐标
            downX = event.getRawX();
            downY = event.getRawY();
            // 更新移动的坐标
            updateMoveCenter(downX, downY);
            break;
        case MotionEvent.ACTION_MOVE:
            // 得到按下的坐标
            downX = event.getRawX();
            downY = event.getRawY();
            // 更新移动的坐标
            updateMoveCenter(downX, downY);

            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:

            break;

        }
        return true;
    }

    /**
     * 更新移动的点
     * 
     * @param downX
     * @param downY
     */
    public void updateMoveCenter(float downX, float downY) {
        mMovewCenter.set(downX, downY);
        invalidate();
    }

2、此时看,我们的手总是离大圆圈有一点距离,这个距离就是状态栏的高度,如何才能让手指在中间呢?
方法1:此时必须先得到状态栏的高度

/**
     * 获取状态栏高度
     * 
     * @param v
     * @return
     */
    public static int getStatusBarHeight(View v) {
        if (v == null) {
            return 0;
        }
        Rect frame = new Rect();
        v.getWindowVisibleDisplayFrame(frame);
        return frame.top;
    }

如果画布进行了平移或旋转动画,我们必须在平移钱,保存,在绘制完成后恢复

canvas.save();
canvas.translate(0, -mBarHeight);
canvas.restore();

方法2:不使用getRawX,而是使用getX,这样的话就不存在状态栏的问题了

downX = event.getX();
downY = event.getY();
// 更新移动的坐标
updateMoveCenter(downX, downY);

3、当大圆圈移动到一定距离时,会断开,此时如果松手了,消失
怎么才能得到两个圆心的距离呢?我们以前学过勾股定理
就是:(y1-y2)的平方 + (x1-x2)的平方的和然后开根号

/**
     * As meaning of method name. 获得两点之间的距离
     * 
     * @param p0
     * @param p1
     * @return
     */
    public static float getDistanceBetween2Points(PointF p0, PointF p1) {
        float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2));
        return distance;
    }

4、当移动时,小圆圈的半径会随着拉动的距离变化,如何实现

// 获取固定圆半径(根据两圆圆心距离)
    private float getTempStickRadius() {
        float distance = GeometryUtil.getDistanceBetween2Points(mMovewCenter, mStaticCenter);

        // if(distance> farestDistance){
        // distance = farestDistance;
        // }
        distance = Math.min(distance, mMaxDistance);

        // 0.0f -> 1.0f
        float percent = distance / mMaxDistance;

        // percent , 100% -> 20%
        return ValueUtil.evalute(percent, mStaicRadius, mStaicRadius * 0.2f);
    }

然后在 onDraw里把得到的值设置到

float tempStaticRadius = getTempStickRadius();
canvas.drawCircle(mStaticCenter.x, mStaticCenter.y, tempStaticRadius, mPaint);

5、现在实现到了一定距离后,断开,在onTouchevent的move事件里判断

//当超过最大值时断开
float distance = GeometryUtil.getDistanceBetween2Points(mMovewCenter, mStaticCenter);
if (distance > mMaxDistance) {
    isOutRange = true;
    invalidate();
}

然后在onDraw里判断

if (!isOutRange) {
    //如果没有超出范围才绘制

6、超出范围后,是否消失的实现在MotionEvent.ACTION_UP里实现,这里有三种可能,上面已经说了

if (isOutRange) {
//1)拖拽超出范围时,断开了,此时我们松手,图标消失
// 当超过最大值时断开
distance = GeometryUtil.getDistanceBetween2Points(mMovewCenter, mStaticCenter);
if (distance > mMaxDistance) {
    isDisappear = true;
    invalidate();
}else {
    //2)拖拽超出范围时,断开了,此时我们把图标移动回去,图标恢复原样
    //就是把移动的圆圈设置到原来的静态圆圈里
    updateMoveCenter(mStaticCenter.x, mStaticCenter.y);
}

}else {
//3)拖拽没有超出范围时,此时我们松手,图标弹回去

//得到固定的点
final PointF tempMovePointF = new PointF(mMovewCenter.x, mMovewCenter.y);
ValueAnimator vAnim = ValueAnimator.ofFloat(1.0f);
vAnim.addUpdateListener(new AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float percent = animation.getAnimatedFraction();
        //得到两点的
        PointF pointF = GeometryUtil.getPointByPercent(tempMovePointF, mStaticCenter, percent);
        updateMoveCenter(pointF.x, pointF.y);
    }
});
vAnim.setInterpolator(new OvershootInterpolator(4));
vAnim.setDuration(500);
vAnim.start();
}

到此,这个控件基本完成了,

六、源码下载

链接:http://pan.baidu.com/s/1nvKpyad 密码:rniy

———————————————————————
(java 架构师全套教程,共760G, 让你从零到架构师,每月轻松拿3万)
有需求者请进站查看,非诚勿扰

https://item.taobao.com/item.htm?spm=686.1000925.0.0.4a155084hc8wek&id=555888526201

01.高级架构师四十二个阶段高
02.Java高级系统培训架构课程148课时
03.Java高级互联网架构师课程
04.Java互联网架构Netty、Nio、Mina等-视频教程
05.Java高级架构设计2016整理-视频教程
06.架构师基础、高级片
07.Java架构师必修linux运维系列课程
08.Java高级系统培训架构课程116课时
(送:hadoop系列教程,java设计模式与数据结构, Spring Cloud微服务, SpringBoot入门)
——————————————————————–

你可能感兴趣的:(android,自定义view)