转载请注明出处:http://blog.csdn.net/bbld_/article/details/41246247 【Rocko's
blog

之前几天下载了非常久没用了的桌面版酷狗来用用的时候,发现当中载入歌曲的等待进度条的效果不错(个人感觉)。例如以下:

然后趁着这周末两天天气较冷,窝在宿舍放下成堆的操作系统作业(目測要抄一节多课的一堆堆文字了啊...啊..)毅然决定把它鼓捣出来,终于的效果例如以下(总感觉有点不和谐啊·):

对照能看出来的就是多了形状的选择还有使用图片了。那么接下来就是它的实现过程。

对自己定义View实现还不明确的建议看下郭神的博客(View系列4篇): Android LayoutInflater原理分析,带你一步步深入了解View(一) 和大苞米的这篇:ANDROID自己定义视图——onMeasure。MeasureSpec源代码
流程 思路具体解释

自己定义属性

自己定义View一般都要用到view本身的属性了,重写现有的控件则不用。额,然后我们的这个BounceProgressBar须要什么特有的属性呢?首先要明白的是这里BounceProgressBar没有提供详细进度表现的实现的。

再详细想想:它须要每一个图像的大小,叫singleSrcSize,类型就是dimension了;上下跳动的速度。叫speed。类型为integer;形状,叫shape,类型为枚举类型,提供这几个形状的实现,original、circle、pentagon、rhombus、heart都是见名知意的了;最后是须要的图片资源。叫src,类型为reference|color。即能够是drawable里的图片或颜色值。

有了须要的属性后,在values目录下建个资源文件(名字任意,见名知意就好)来定义这些属性了,例如以下。代码可能有些英文,并且水平有些渣,只是一般前面都会解释了的:

<?

xml version="1.0" encoding="utf-8"?

>
<resources> <declare-styleable name="BounceProgressBar"> <!-- the single child size -->
<attr name="singleSrcSize" format="dimension" />
<!-- the bounce animation one-way duration -->
<attr name="speed" format="integer" />
<!-- the child count 。本来还想能自己定义个数的,可是临时个人实现起来有些麻烦,所以先不加这个-->
<!-- <attr name="count" format="integer" min="1" /> -->
<!-- the progress child shape -->
<attr name="shape" format="enum">
<enum name="original" value="0" />
<enum name="circle" value="1" />
<enum name="pentagon" value="2" />
<enum name="rhombus" value="3" />
<enum name="heart" value="4" />
</attr>
<!-- the progress drawable resource -->
<attr name="src" format="reference|color"></attr>
</declare-styleable> </resources>

然后先把BounceProgressBar类写出来例如以下:

public class BounceProgressBar extends View {
//...
}

如今就能够在布局里用我们的BounceProgressBar了。这里须要注意的是。我们须要加上以下代码第二行命名空间才干使用我们的属性,也能够把它放到根元素的属性里。

        <org.roc.bounceprogressbar.BounceProgressBar
xmlns:bpb="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
bpb:shape="circle"
bpb:singleSrcSize="8dp"
bpb:speed="250"
bpb:src="#6495ED" />

自己定义了属性最后我们要做的就是在代码里去获取它了,在哪里获取呢,当然是BounceProgressBar类的构造方法里了,相关代码例如以下:

	public BounceProgressBar(Context context) {
this(context, null, 0);
} public BounceProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public BounceProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
} private void init(AttributeSet attrs) {
if (null == attrs) {
return;
}
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BounceProgressBar);
speed = a.getInt(R.styleable.BounceProgressBar_speed, 250);
size = a.getDimensionPixelSize(R.styleable.BounceProgressBar_singleSrcSize, 50);
shape = a.getInt(R.styleable.BounceProgressBar_shape, 0);
src = a.getDrawable(R.styleable.BounceProgressBar_src);
a.recycle();
}

得到属性还是比較简单的,记得把TypedArray回收掉。

首先是获得我们定义的TypedArray。然后是一个一个的去get属性值。然后可能有人要说了,我明明没定义R.styleable.BounceProgressBar_xxx这些东西啊。事实上呢这是Android自己主动给我们生成的declare-styleable里的每一个属性的在TypedArray里的index相应位置的,你是找不到类似R.styleable.speed这样的东西存在的,它又是怎么相应的呢,点进去看一下R文件就知道了,R.styleable.BounceProgressBar_speed的值是1,由于speed是第2个属性(0,1..),所以你确定属性的位置直接写a.getInt(1,
250)也是能够的。

第二个參数是默认值。

图形的形状

得到属性值后。我们就能够去做对应的处理操作了,这里是图形形状的获取,用到了shapesrcsize属性。speed和size在下一点中也会讲到。

首先我们观察到三个图片是有些渐变的效果的。我这里仅仅是简单地做透明度处理,即一次变透明,效果是能够在处理好一点,可能之后再优化了。从src得到的图片资源是Drawable的,不管是ColorDrawable或是BitmapDrawable。我们须要先把它转换成size大小的Bitmap,再用canvas对它进行形状裁剪操作。至于为什么要先转Bitmap呢,这是我的做法。再看完以下的操作后假设有更好的方式希望能够交流一下。

	/**
* Drawable → Bitmap(the size is "size")
*/
private Bitmap drawable2Bitmap(Drawable drawable) {
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, size, size);
drawable.draw(canvas);
return bitmap;
}

Bitmap得到了,形状呢我们就能够进行操作了,我们先说圆形circle、菱形rhombus、五角星pentagon,再说心形heart。由于处理方式有些不同。

像其他ShapeImageView我看到好像喜欢用svg来处理。看了他们的代码,比如这个:https://github.com/siyamed/android-shape-imageview 
貌似有些麻烦。相比之下我的处理比較简单。

圆形circle、菱形rhombus、五角星pentagon

这些形状都能够使用ShapeDrawable来得到。我们须要BitmapShader渲染器,这是ShapeDrawable的Paint画笔须要的,再须要一个空的位图Bitmap,再一个 Canvas。

例如以下:

		BitmapShader bitmapShader = new BitmapShader(srcBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Path path;
ShapeDrawable shapeDrawable = new ShapeDrawable();
shapeDrawable.getPaint().setAntiAlias(true);
shapeDrawable.getPaint().setShader(bitmapShader);
shapeDrawable.setBounds(0, 0, size, size);
shapeDrawable.setAlpha(alpha);

Canvas是ShapeDrawable上的画布。BitmapShader是ShapeDrawable画笔Paint的的渲染器。用来渲染处理图形(由src的drawable转换得到的bitmap),渲染模式选用了CLAMP。意思是 假设渲染器超出原始边界范围,会复制范围内边缘染色。

圆形呢,我们直接用现成的就能够:

shapeDrawable.setShape(new OvalShape());

这个ShapeDrawable画出来的就是圆形了,当然要调用shapeDrawable.draw(canvas);方法了,这样bitmap就会变成圆形的srcBitmap(方法传进的參数)了,这方法的完整代码后面给出。

菱形呢,我们则这样子:

			path = new Path();
path.moveTo(size / 2, 0);
path.lineTo(0, size / 2);
path.lineTo(size / 2, size);
path.lineTo(size, size / 2);
path.close();
shapeDrawable.setShape(new PathShape(path, size, size));

就是边长为size的正方形。取每条边的中点,四个点连起来就是了。我们知道Android的坐标一般都是屏幕左上角顶点为坐标原点的,坐标点找到了我们把path连接起来即close。

这样PathShape就是一个菱形了。多边形差点儿相同都能够这么画的,以下的五角形也是一样。

说明:这里全部图形的绘制都是在边长size的正方形里。

五角形的原理也是用PathShape,仅仅是它须要的坐标点有点多啊。须要细致计算慢慢调试。

			path = new Path();
// The Angle of the pentagram
float radian = (float) (Math.PI * 36 / 180);
float radius = size / 2;
// In the middle of the radius of the pentagon
float radius_in = (float) (radius * Math.sin(radian / 2) / Math.cos(radian));
// The starting point of the polygon
path.moveTo((float) (radius * Math.cos(radian / 2)), 0);
path.lineTo((float) (radius * Math.cos(radian / 2) + radius_in * Math.sin(radian)),
(float) (radius - radius * Math.sin(radian / 2)));
path.lineTo((float) (radius * Math.cos(radian / 2) * 2),
(float) (radius - radius * Math.sin(radian / 2)));
path.lineTo((float) (radius * Math.cos(radian / 2) + radius_in * Math.cos(radian / 2)),
(float) (radius + radius_in * Math.sin(radian / 2)));
path.lineTo((float) (radius * Math.cos(radian / 2) + radius * Math.sin(radian)),
(float) (radius + radius * Math.cos(radian)));
path.lineTo((float) (radius * Math.cos(radian / 2)), (float) (radius + radius_in));
path.lineTo((float) (radius * Math.cos(radian / 2) - radius * Math.sin(radian)),
(float) (radius + radius * Math.cos(radian)));
path.lineTo((float) (radius * Math.cos(radian / 2) - radius_in * Math.cos(radian / 2)),
(float) (radius + radius_in * Math.sin(radian / 2)));
path.lineTo(0, (float) (radius - radius * Math.sin(radian / 2)));
path.lineTo((float) (radius * Math.cos(radian / 2) - radius_in * Math.sin(radian)),
(float) (radius - radius * Math.sin(radian / 2)));
path.close();// Make these points closed polygons
shapeDrawable.setShape(new PathShape(path, size, size));

连线果然有点多啊。

。这里的绘制五角形是先依据指定的五角形的角的角度还有半径,然后确定连线起点。再连下一点...最后封闭,一不小心就不知道连到哪去了。。



心形heart

path来画心形就不能连直线实现了。刚開始是使用path的quadTo(x1, y1, x2, y2)方法来画贝塞尔曲线来实现的,发现画出来的形状不饱满,更像一个锥形(脑补),所以就放弃这样的方式了。然后找到了这篇关于画心形的介绍Heart Curve,然后就採用他的第四种方法(例如以下图),即採用两个椭圆形状来裁剪实现。

1、画一个椭圆形状

   //canvas bitmap bitmapshader等。上面代码已有
path = new Path();
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(bitmapShader);
Matrix matrix = new Matrix(); //控制旋转
Region region = new Region();//裁剪一段图形区域
RectF ovalRect = new RectF(size / 4, 0, size - (size / 4), size);
path.addOval(ovalRect, Path.Direction.CW);

2、旋转图形。大概45度左右

   matrix.postRotate(42, size / 2, size / 2);
path.transform(matrix, path);

3、选取旋转后的右半部分图形,并用cancas画出这半边的心形

			path.transform(matrix, path);
region.setPath(path, new Region((int) size / 2, 0, (int) size, (int) size));
canvas.drawPath(region.getBoundaryPath(), paint);

4、反复1、2、3同一时候改变方向角度和裁剪的区域

			matrix.reset();
path.reset();
path.addOval(ovalRect, Path.Direction.CW);
matrix.postRotate(-42, size / 2, size / 2);
path.transform(matrix, path);
region.setPath(path, new Region(0, 0, (int) size / 2, (int) size));
canvas.drawPath(region.getBoundaryPath(), paint);

这样我们便完毕心形图片的裁剪工作了。得到的bitmap就变成心形了:

    这个心能够见人了。。

画完心就该下一步了。

View的绘制

说到view的绘制过程就须要以下三部曲了:

  • 測量——onMeasure():决定View的大小
  • 布局——onLayout():决定View在ViewGroup中的位置
  • 绘制——onDraw():怎样绘制这个View。

    測量

    对于BounceProgressBar控件的測量还是比較简单的。当wrap_content时高度和宽度分别为size的5倍和4倍,其他情况时就指定宽高为详细測量到的值就好。然后决定三个图形在控件之中的水平位置:

    	@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
    int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
    int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
    int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
    setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? mWidth = sizeWidth : mWidth,
    (modeHeight == MeasureSpec.EXACTLY) ? mHeight = sizeHeight : mHeight); firstDX = mWidth / 4 - size / 2;//第一个图形的水平位置
    secondDX = mWidth / 2 - size / 2;//...
    thirdDX = 3 * mWidth / 4 - size / 2;//...
    }

    当有指定了详细值的宽高时,mWidth和mHeight也设置应为測量到的sizeWidth和sizeHeight。

    布局

    说到布局时先明白一点的是图像的跳动是通过属性动画来控制的,属性动画是什么?我一句话说一下就是:能够以动画的效果形式去更改一个对象的某个属性。还不太了解的能够先找找资料看一下。

    布局这里就决定视图里的各种位置的操作了,作为单个控件时一般不怎么用到。我在这里进行动画的初始化并開始的操作了。能够看到我们的BounceProgressBar是三个图形在跳动的。

    三个属性的封装例如以下:

    	/**
    * firstBitmapTop's Property. The change of the height through canvas is
    * onDraw() method.
    */
    private Property<BounceProgressBar, Integer> firstBitmapTopProperty = new Property<BounceProgressBar, Integer>(
    Integer.class, "firstDrawableTop") {
    @Override
    public Integer get(BounceProgressBar obj) {
    return obj.firstBitmapTop;
    } public void set(BounceProgressBar obj, Integer value) {
    obj.firstBitmapTop = value;
    invalidate();
    };
    };
    /**
    * secondBitmapTop's Property. The change of the height through canvas is
    * onDraw() method.
    */
    private Property<BounceProgressBar, Integer> secondBitmapTopProperty = new Property<BounceProgressBar, Integer>(
    Integer.class, "secondDrawableTop") {
    @Override
    public Integer get(BounceProgressBar obj) {
    return obj.secondBitmapTop;
    } public void set(BounceProgressBar obj, Integer value) {
    obj.secondBitmapTop = value;
    invalidate();
    };
    };
    /**
    * thirdBitmapTop's Property. The change of the height through canvas is
    * onDraw() method.
    */
    private Property<BounceProgressBar, Integer> thirdBitmapTopProperty = new Property<BounceProgressBar, Integer>(
    Integer.class, "thirdDrawableTop") {
    @Override
    public Integer get(BounceProgressBar obj) {
    return obj.thirdBitmapTop;
    } public void set(BounceProgressBar obj, Integer value) {
    obj.thirdBitmapTop = value;
    invalidate();
    };
    };

    onLayout部分的代码例如以下:

    	@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom); if (bouncer == null || !bouncer.isRunning()) {
    ObjectAnimator firstAnimator = initDrawableAnimator(firstBitmapTopProperty, speed, size / 2,
    mHeight - size);
    ObjectAnimator secondAnimator = initDrawableAnimator(secondBitmapTopProperty, speed, size / 2,
    mHeight - size);
    secondAnimator.setStartDelay(100);
    ObjectAnimator thirdAnimator = initDrawableAnimator(thirdBitmapTopProperty, speed, size / 2,
    mHeight - size);
    thirdAnimator.setStartDelay(200);
    bouncer = new AnimatorSet();
    bouncer.playTogether(firstAnimator, secondAnimator, thirdAnimator);
    bouncer.start();
    }
    } private ObjectAnimator initDrawableAnimator(Property<BounceProgressBar, Integer> property, int duration,
    int startValue, int endValue) {
    ObjectAnimator animator = ObjectAnimator.ofInt(this, property, startValue, endValue);
    animator.setDuration(duration);
    animator.setRepeatCount(Animation.INFINITE);
    animator.setRepeatMode(ValueAnimator.REVERSE);
    animator.setInterpolator(new AccelerateInterpolator());
    return animator;
    }

    动画的值变换是从size到mHeight-size的。要减去size的原因是在canvas中,大于(mHeight, mHeight)的左边已经view本身的大小范围了。

    绘制

    绘制这里做的工作不是非常多。就是依据每一个图像的水平位置。和通过属性动画控制的高度来去绘制bitmap在画布上。

    	@Override
    protected synchronized void onDraw(Canvas canvas) {
    /* draw three bitmap */
    firstBitmapMatrix.reset();
    firstBitmapMatrix.postTranslate(firstDX, firstBitmapTop); secondBitmapMatrix.reset();
    secondBitmapMatrix.setTranslate(secondDX, secondBitmapTop); thirdBitmapMatrix.reset();
    thirdBitmapMatrix.setTranslate(thirdDX, thirdBitmapTop); canvas.drawBitmap(firstBitmap, firstBitmapMatrix, mPaint);
    canvas.drawBitmap(secondBitmap, secondBitmapMatrix, mPaint);
    canvas.drawBitmap(thirdBitmap, thirdBitmapMatrix, mPaint);
    }

    位置是通过Matrix来控制的。由于当时还考虑到落地的变形,但如今给去掉先了。

    总的来说绘制的流程是通过属性动画来控制每一个图像在画布上的位置,在属性更改时调用invalidate()方法去通知重绘即可了。看起来就是跳动的效果了。跳动速度的变化则是给动画设置插值器来完毕。



    这篇文章就写到这里了,完整的源代码我放到我的github上了(https://github.com/zhengxiaopeng/BounceProgressBar),欢迎大家star、fork那么它一起。

  • 版权声明:本文博客原创文章。博客,未经同意,不得转载。

    Android 它们的定义View它BounceProgressBar的更多相关文章

    1. 【Android】自己定义View、画家(画布)Canvas与画笔Paint的应用——绘图、涂鸦板app的实现

      利用一个简单的绘图app来说明安卓的图形处理类与自己定义View的应用. 例如以下图,有一个供用户自己随意绘图.涂鸦的app. 这里不做那么花俏了,仅提供黑白两色.但能够改变笔尖的粗细. 实质上这里的 ...

    2. Android 它们的定义View (一)

      转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 非常Android入门程序员AndroidView.可能都是比較恐 ...

    3. Android 开发 -------- 自己定义View 画 五子棋

      自己定义View  实现 五子棋 配图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG92ZV9KYXZjX3lvdQ==/font/5a6L5L2T ...

    4. 【Android】自己定义View

      翻译自:http://developer.android.com/training/custom-views/index.html 一)创建view类 一个设计良好的自己定义view与其它的类一样.它 ...

    5. Android 它们的定义View

      安卓开发过程,安卓官方控制有时来自往往不能满足我们的需求.这一次,我必须定义自己.下面我们就来看看他们的定义View: package com.example.myview; import andro ...

    6. Android 它们的定义View视图

      创建一个新视图将满足我们独特UI需求. 本文介绍的发展将指南针罗盘接口使用UI,通过继承View定义自己的视图类工具,为了深入了解自己的自定义视图. 实现效果图: 源码: 布局文件activity_m ...

    7. 【Android】利用自己定义View的重绘实现拖动移动,获取组件的尺寸

      以下利用一个app来说明怎样利用自己定义View的重绘实现拖动移动.获取组件的尺寸. 例如以下图,触摸拖动,或者轻轻点击屏幕都能移动图片.假设碰到文字,则会弹出提示. 这里是利用自己定义View的重绘 ...

    8. Android 自己定义View (二) 进阶

      转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自己定义View之旅.前面已经介绍过一个自己定义View的基础 ...

    9. 【android自己定义控件】自己定义View属性

      1.自己定义View的属性 2.在View的构造方法中获得我们自己定义的属性 3.重写onMesure 4.重写onDraw 3这个步骤不是必须,当然了大部分情况下还是须要重写的. 1.自己定义Vie ...

    随机推荐

    1. 模仿jquery的一些实现

      wylUtil.js //w作为window的形参,就表示window (function(w) { // 定义一个全局的window.wyl变量,就类似于jquery里的$,Jquery对象 w.w ...

    2. javascript 检测密码强度 美化版

      模仿美团的美化 <!DOCTYPE> <head runat="server"> <title></title> <link ...

    3. python 模块BeautifulSoup使用

      BeautifulSoup是一个专门用于解析html/xml的库.官网:http://www.crummy.com/software/BeautifulSoup/ 说明,BS有了4.x的版本了.官方说 ...

    4. 11997 - K Smallest Sums(优先队列)

      11997 - K Smallest Sums You’re given k arrays, each array has k integers. There are kk ways to pick ...

    5. UVA 10652 Board Wrapping(凸包)

      The small sawmill in Mission, British Columbia, hasdeveloped a brand new way of packaging boards for ...

    6. C语言中没有main函数生成可执行程序的几种方法

      1.define预处理指令 这种方式很简单,只是简单地将main字符串用宏来代替,或者使用##拼接字符串.示例程序如下: #include <stdio.h> #define begin ...

    7. linux脚本:ftp自动传输文件

      使用Shell脚本实现ftp的自动上传下载 http://liwenge.iteye.com/blog/566515 open 192.168.1.171 user guest 123456cd /h ...

    8. dpkg卸载和安装deb

      今天在linux mint上安装个东西,没有安装完全,但是启动的时候能够启动,为了防止以后出现异常,想把它卸载了,在软件上点卸载,没有反应, 如下图: 没有指定卸载的包源,无奈使用sudo apt-g ...

    9. PHPer的等级划分

      PHPer的等级划分 前一段时间刚刚完成PHP的培训,然后想知道自己目前的水平(或者说等级),并且应该在哪些方面进行提高,所以在网上查了一下相关介绍.其中有一篇介绍讲的挺清楚的,至少目前的我还是很认同 ...

    10. 为什么要用BASE64

      BASE64和其他相似的编码算法通常用于转换二进制数据为文本数据,其目的是为了简化存储或传输.更具体地说,BASE64算法主要用于转换二进 制数据为ASCII字符串格式.Java语言提供了一个非常好的 ...