Android自定义Layout实现QQ消息拖动效果

来源:互联网 发布:seo专员做什么的 编辑:程序博客网 时间:2024/05/21 08:48

Android自定义Layout实现QQ消息拖动效果


前些天看到有人发了片类似的文章,然后今天没事做就自己动手,搞了一个。
作为刚入门的新手,感觉有必要练练手。
先给大家看看效果。


接着分析下思路
自定义一个组件/布局
  1. 重写Draw方法
  2. 贝塞尔曲线
  3. 动画
第一种
第二种

首先了解下原理—— 一个小圆,一个大圆,外加两条贝塞尔曲线,然后填充即可。
两个圆还好,只要知道半径和圆心坐标即可。我们主要分析下这两条贝塞尔曲线应该怎么画,
在这里我就不介绍贝塞尔曲线了,大家可以去百度查查贝塞尔曲线绘制原理,
画一条贝塞尔曲线需要两个数据点,和一个控制点,这三个点应该怎么确定呢?
通过我画的图,应该大家马上就能知道了,a1-b1即为贝塞尔曲线的数据点,连接两个圆的圆心,
这条线的一半即可作为控制点,接下来就是计算a1 a2 b1 b2 以及控制点的坐标了,这个我就不啰嗦了,
无非就是三角函数,如果忘记的画,百度上复习下即可。

既然知道原理了,那么我们应该在哪里画呢?
有人可能会说自定义View即可,其实是错误的,View是一个控件 他是有一个范围的,比如一个按钮,
我们总不可能做出这样一个东西,只能在按钮范围里面拖动吧。
所以正在的做法是自定义一个ViewGroup,作为底层布局。
接着,很多同学可能会直接重写onDraw方法,接在在onDraw方法里面画,其实这样做也是错误的,为什么呢?

 因为你画完后,然后在这个布局里面添加其他控件,你会发现你画的东西在控件地下,相当于背景了,
我们的本意是需要他绘制在顶层,这并不满足我们的要求,所以这里大家要注意了。
其实onDraw本来就是用来画背景的,
按照源码的注释,View的绘制过程是这样子的:
/* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * *      1. Draw the background *      2. If necessary, save the canvas' layers to prepare for fading *      3. Draw view's content *      4. Draw children *      5. If necessary, draw the fading edges and restore layers *      6. Draw decorations (scrollbars for instance) */

先绘制背景,如果有必要,保存画布层,绘制内容,绘制孩子,绘制渐变,绘制装饰物。 
由此可知,我们应该通过绘制子孩子,来达到绘制在顶层 
我们正在需要重写的方法应该是dispatchDraw(),绘制方法同onDraw一样。

代码实现
首先计算各点坐标
第一种方法,通过三角函数计算
<span style="white-space:pre"></span>    // 获得角度α            x = (float) Math.atan((control.y - centerY) / (control.x - centerX));            // 获取定点到移动点的距离            distance = (float) Math.sqrt(Math.pow((control.y - centerY), 2) + Math.pow((control.x - centerX), 2));            //  第一个圆圈随着distance距离的增加而缩小            r = 70 * (1 - distance / 1000);            // 第一个圆圈的贝塞尔曲线点            a1.set(centerX - r * (float) Math.sin(x), centerY + r * (float) Math.cos(x));            a2.set(centerX + r * (float) Math.sin(x), centerY - r * (float) Math.cos(x));            // 第二个圆圈的贝塞尔曲线点            b1.set(control.x - R * (float) Math.sin(x), control.y + R * (float) Math.cos(x));            b2.set(control.x + R * (float) Math.sin(x), control.y - R * (float) Math.cos(x));            // 贝塞尔曲线控制点            half_control.set((centerX + control.x) / 2, (centerY + control.y) / 2);

第二种方法,通过相似三角形计算
  <span style="white-space:pre"></span>    // 获取定点到移动点的距离            distance = (float) Math.sqrt(Math.pow((control.y - centerY), 2) + Math.pow((control.x - centerX), 2));            //  第一个圆圈随着distance距离的增加而缩小            r = 70 * (1 - distance / 1000);            // 第一个圆圈的贝塞尔曲线点            a1.set(centerX - r / distance * (control.y - centerY), centerY + r / distance * (control.x - centerX));            a2.set(centerX + r / distance * (control.y - centerY), centerY - r / distance * (control.x - centerX));            // 第二个圆圈的贝塞尔曲线点            b1.set(control.x - R / distance * (control.y - centerY), control.y + R / distance * (control.x - centerX));            b2.set(control.x + R / distance * (control.y - centerY), control.y - R / distance * (control.x - centerX));            // 贝塞尔曲线控制点            half_control.set((centerX + control.x) / 2, (centerY + control.y) / 2);


第一个画圆还好,直接给定半径以及圆心坐标,然后调用drawCircle()方法即可,
   // 画第一个圆   canvas.drawCircle(centerX, centerY, r, mPaint);
在这里,我们看到一个mPaint参数,这个就是我们所谓的画笔
   // 画笔属性设置   Paint mPaint = new Paint;   mPaint.setAntiAlias(true);  // 设置没有锯齿   //mPaint.setStyle(Paint.Style.STROKE);  //空心   mPaint.setStyle(Paint.Style.FILL);  //实心   mPaint.setColor(Color.RED);   mPaint.setStrokeWidth(4);
第二个圆,也就是我们拖动的圆,绘制方法同第一个圆一样,只是这个圆的圆心是跟随着我们的手变化的,所以圆心坐标也是变化的。
这个坐标可以通过onTouchEvent来获取
@Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_MOVE:                // 根据触摸位置更新控制点,并提示重绘                control.x = event.getX();                control.y = event.getY();                invalidate();   //重绘view                break;            default:                break;<span style="white-space:pre"></span>return true    }
画贝塞尔曲线,
     mPath.reset();     mPath.moveTo(a1.x, a1.y);     mPath.quadTo(half_control.x, half_control.y, b1.x, b1.y);     mPath.lineTo(b2.x, b2.y);     mPath.quadTo(half_control.x, half_control.y, a2.x, a2.y);     mPath.close();     canvas.drawPath(mPath, mPaint);
注意事项,切记不可在Draw方法里new对象,因为,当我们重绘View时,就会生成非常多浪费的对象。

最后就是动画了
动画部分也很简单,直接用ValuAnimator
    /**     * 自定义动画回弹     */    public void MyAnimator(int startX, int startY, final int endX, int endY, int time) {        ValueAnimator xValueAnimator = ValueAnimator.ofInt(startX, endX);<span style="white-space:pre"></span>//动画开始到结束        ValueAnimator yValueAnimator = ValueAnimator.ofInt(startY, endY);        xValueAnimator.setDuration(time);<span style="white-space:pre"></span>//设置动画时间        yValueAnimator.setDuration(time);        xValueAnimator.setInterpolator(new OvershootInterpolator());<span style="white-space:pre"></span>//动画效果        yValueAnimator.setInterpolator(new OvershootInterpolator());        xValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                control.x = (int) animation.getAnimatedValue();<span style="white-space:pre"></span>//获取动画变动的过程中的值                if (control.x == endX) {                    flog = true;                }                invalidate();            }        });        yValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                control.y = (int) animation.getAnimatedValue();                invalidate();            }        });        xValueAnimator.start();        yValueAnimator.start();    }
动画:
  AccelerateDecelerateInterpolator 在动画开始与结束的地方速率改变比较慢,在中间的时候加速
  AccelerateInterpolator  在动画开始的地方速率改变比较慢,然后开始加速
  AnticipateInterpolator 开始的时候向后然后向前甩
  AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值
  BounceInterpolator   动画结束的时候弹起
  CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线
  DecelerateInterpolator 在动画开始的地方快然后慢
  LinearInterpolator   以常量速率改变
  OvershootInterpolator    向前甩一定值后再回到原来位置

如果android定义的interpolators不符合你的效果也可以自定义interpolators

源码下载:http://download.csdn.net/detail/qq970259858/9549605



0 0
原创粉丝点击