Android 自定义 View 之 LeavesLoading

//逆时针
leaf.rotateDir = RotateDir.ANTICLOCKWISE;
break;
default:
//顺时针
leaf.rotateDir = RotateDir.CLOCKWISE;
break;
}
//随机起始角度
leaf.rotateAngle = mRandom.nextInt(360);
leaf.n = mRandom.nextInt(20);
mAddTime += mRandom.nextInt((int)mLeafFloatTime);
leaf.startTime = System.currentTimeMillis() + mAddTime;
return leaf;
}

3.2 叶子飘动轨迹为正弦函数

确定 Leaf 在某个时刻的坐标 ( x , y ):

/**

  • 获取叶子的(x,y)位置
  • @param leaf 叶子
  • @param currentTime 当前时间
    */
    private void getLeafLocation(Leaf leaf,long currentTime){
    long intervalTime = currentTime - leaf.startTime;//飘动时长
    if (intervalTime <= 0){
    // 此 Leaf 还没到飘动时间
    return;
    }else if (intervalTime > mLeafFloatTime){
    // Leaf 的飘动时间大于指定的飘动时间,即叶子飘动到了最左边,应回到最右边
    leaf.startTime = currentTime + new Random().nextInt((int)mLeafFloatTime);
    }
    // 计算移动因子
    float fraction = (float) intervalTime / mLeafFloatTime;
    leaf.x = (1-fraction)*mProgressLen;
    leaf.y = getLeafLocationY(leaf);

if (leaf.x <= mYellowOvalHeight / 4){
//叶子飘到最左边,有可能会超出 RoundRect 边界,所以提前特殊处理
leaf.startTime = currentTime + new Random().nextInt((int)mLeafFloatTime);
leaf.x = mProgressLen;
leaf.y = getLeafLocationY(leaf);
}
}

要想让 Leaf 飘动轨迹为正弦函数,关键在于确定 Leaf 的 Y 轴坐标:

/**

  • 获取叶子的Y轴坐标
  • @param leaf 叶子
  • @return 经过计算的叶子Y轴坐标
    /
    private float getLeafLocationY(Leaf leaf){
    float w = (float) (Math.PI * 2 / mProgressLen);//角频率
    float A;//计算振幅值
    switch (leaf.type){
    case LITTLE:
    A = mLeafLen/3;
    break;
    case MIDDLE:
    A = mLeafLen
    2/3;
    break;
    default:
    A = mLeafLen;
    break;
    }
    // (mHeight-mLeafLen)/2 是为了让 Leaf 的Y轴起始位置居中
    return (float) (A * Math.sin(w * leaf.x + leaf.n)+(mHeight-mLeafLen)/2);
    }

3.3 叶子飘动时自旋转

这里就涉及到了 Leaf 的绘制,其实 Gif 中的叶子和风扇都可以使用 Canves 直接绘制图案,但是这样就会有两个问题:

  1. 难画:想要画出满意图形,并且还要旋转、缩放、平移可要下一番功夫。
  2. 灵活性低:如果想换其他样式又得重新设计绘制过程。

因此这里采用 Canves.drawBitmap() 的方式绘制,直接使用已有的图片作为叶子和风扇,同时利用 Canves.drawBitmap() 的一个重载的方法可以很方便的实现旋转、缩放、平移:

void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) ;

就是通过这里的 Matrix 矩阵,它内部封装了 postScale()postTranslatepostRotate() 等方法,可以帮助我们快速的对 Bitmap 进行旋转、缩放、平移还有其他操作。使用时要记得配合 Canves 的 save()restore() 使用,否则达不到想要的效果。

对这方面不熟的朋友可以看看 HenCoder 的自定义 View 教学 1-4

绘制 Leaf 的方法:

private void drawLeaves(Canvas canvas){
long currentTime = System.currentTimeMillis();
for (Leaf leaf : mLeafList) {
if (currentTime > leaf.startTime && leaf.startTime != 0){
// 获取 leaf 当前的坐标
getLeafLocation(leaf,currentTime);
canvas.save();
Matrix matrix = new Matrix();
// 缩放 自适应 View 的大小
float scaleX = (float) mLeafLen / mLeafBitmapWidth;
float scaleY = (float) mLeafLen / mLeafBitmapHeight;
matrix.postScale(scaleX,scaleY);
// 位移
float transX = leaf.x;
float transY = leaf.y;
matrix.postTranslate(transX,transY);
// 旋转
// 计算旋转因子
float rotateFraction = ((currentTime - leaf.startTime) % mLeafRotateTime)
/(float)mLeafRotateTime;
float rotate;
switch (leaf.rotateDir){
case CLOCKWISE:
//顺时针
rotate = rotateFraction * 360 + leaf.rotateAngle;
break;
default:
//逆时针
rotate = -rotateFraction * 360 + leaf.rotateAngle;
break;
}
// 旋转中心选择 Leaf 的中心坐标
matrix.postRotate(rotate,transX + mLeafLen / 2,transY + mLeafLen / 2);
canvas.drawBitmap(mLeafBitmap,matrix,mBitmapPaint);
canvas.restore();
}
}

3.4 Loading == 100% 出现动画

增加一个判断字段 isLoadingCompleted ,在 onDraw() 中选择对应绘制策略。

isLoadingCompleted 在 setProgress() 中根据 progress 设置:

/**

  • 设置进度(自动刷新)
  • @param progress 0-100
    */
    public void setProgress(int progress){
    if (progress < 0){
    mProgress = 0;
    }else if (progress > 100){
    mProgress = 100;
    }else {
    mProgress = progress;
    }
    if (progress == 100){
    isLoadingCompleted = true;
    }else {
    isLoadingCompleted = false;
    }
    // 255 不透明
    mCompletedFanPaint.setAlpha(255);
    postInvalidate();
    }

LeavesLoading.onDraw() 部分实现:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

if (isLoadingCompleted){
//绘制加载完成特效
drawCompleted(canvas);
}else {
//绘制扇叶
drawFan(canvas,mFanLen,mBitmapPaint);
}
//刷新
postInvalidate();
}

drawCompleted() 实现:

private void drawCompleted(Canvas canvas) {
// 每次绘制风扇透明度递减10
int alpha = mCompletedFanPaint.getAlpha() - 10;
if (alpha <= 0){
alpha = 0;
}
mCompletedFanPaint.setAlpha(alpha);
// 文字透明度刚好与风扇相反
mCompletedTextPaint.setAlpha(255-alpha);
// 计算透明因子
float fraction = alpha / 255f;
// 叶片大小 和 文字大小 也是相反变化的
float fanLen = fraction * mFanLen;
float textSize = (1 - fraction) * mCompletedTextSize;
mCompletedTextPaint.setTextSize(textSize);
//测量文字占用空间
Rect bounds = new Rect();
mCompletedTextPaint.getTextBounds(
LOADING_COMPLETED,
0,
LOADING_COMPLETED.length(),
bounds);
// 与 drawLeaf() 相似,不再赘述
drawFan(canvas, (int) fanLen, mCompletedFanPaint);
//画文字
canvas.drawText(
LOADING_COMPLETED,
0,
LOADING_COMPLETED.length(),

结尾

如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?为此我整理了一份Android学习资料路线:

这里是一份BAT大厂面试资料专题包:

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

(img-e9gepQqE-1718903165812)]

这里是一份BAT大厂面试资料专题包:

[外链图片转存中…(img-G95ZIRQy-1718903165812)]

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TypeArray 是 Android 中的一个特殊的资源类型,用于在 XML 中声明自定义 View 的属性。使用 TypeArray 可以方便地在 XML 布局中指定 View 的属性,而不需要在 Java 代码中进行硬编码。 使用 TypeArray 的步骤如下: 1. 在 res/values/attrs.xml 文件中定义定义 View 的属性。 ```xml <resources> <declare-styleable name="MyCustomView"> <attr name="customAttr1" format="integer" /> <attr name="customAttr2" format="string" /> <attr name="customAttr3" format="boolean" /> </declare-styleable> </resources> ``` 2. 在自定义 View 的构造函数中获取 TypedArray 对象,并从中获取属性值。 ```java public class MyCustomView extends View { private int customAttr1; private String customAttr2; private boolean customAttr3; public MyCustomView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView); customAttr1 = a.getInt(R.styleable.MyCustomView_customAttr1, 0); customAttr2 = a.getString(R.styleable.MyCustomView_customAttr2); customAttr3 = a.getBoolean(R.styleable.MyCustomView_customAttr3, false); a.recycle(); } } ``` 在上面的代码中,`context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)` 用于获取 TypedArray 对象,`R.styleable.MyCustomView` 是在 attrs.xml 文件中定义的自定义属性集合,`a.getInt()`、`a.getString()`、`a.getBoolean()` 用于从 TypedArray 对象中获取属性值,最后需要调用 `a.recycle()` 来回收 TypedArray 对象。 3. 在 XML 布局中使用自定义 View,并设置属性值。 ```xml <com.example.MyCustomView android:layout_width="match_parent" android:layout_height="wrap_content" app:customAttr1="123" app:customAttr2="hello" app:customAttr3="true" /> ``` 在上面的代码中,`app:customAttr1`、`app:customAttr2`、`app:customAttr3` 是在 attrs.xml 文件中定义的自定义属性名,可以在 XML 布局中使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值