android自定义view案例,自定义动态View:Android 自定义View实例

前言

Android 开发中自定义View的重要性不言而喻,这里就结合github上一个自定义view的代码来进行分析,代码中提供了动态效果,使用两种方式来生成了自定义View。

效果图:

4eba409dcc17

image

以下是介绍。

自定义View

在安卓开发艺术探索中,自定义View被分为4类

继承View重写onDraw方法

继承ViewGroup派生特殊的Layout

继承特定的View

继承特定的ViewGroup

这里就使用上述1和4来实现效果。

继承View重写onDraw方法

这种方法主要用于实现一些不规则的效果,需要自己重写onDraw方法,同时需要自己支持wrap_content,并且padding也需要自己处理,这里因为实现的两个View会添加到一个重写的ViewGroup里,所以没有实现。

首先是简单的Solid:

class Solid extends View {

// This is a Rect to cover the main view

private Paint aboveWavePaint;

private Paint blowWavePaint;

public Solid(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public Solid(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);

// 这里为1则在LinearLayout中会填满剩余空间

params.weight = 1;

setLayoutParams(params);

}

public void setAboveWavePaint(Paint aboveWavePaint) {

this.aboveWavePaint = aboveWavePaint;

}

public void setBlowWavePaint(Paint blowWavePaint) {

this.blowWavePaint = blowWavePaint;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawRect(getLeft(), 0, getRight(), getBottom(), blowWavePaint);

canvas.drawRect(getLeft(), 0, getRight(), getBottom(), aboveWavePaint);

}

}

Solid实现的效果很简单,从onDraw中可以看到,Solid画了两个正方形,效果是实现了WaveView的下面部分,也就是波浪线的下方。

接下来是Wave,波浪线,不重要的代码被去除了,只关注最重要的部分:

// y=Asin(ωx+φ)+k

class Wave extends View {

......

// ω

private double omega;

public Wave(Context context, AttributeSet attrs) {

this(context, attrs, R.attr.waveViewStyle);

}

public Wave(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawPath(mBlowWavePath, mBlowWavePaint);

canvas.drawPath(mAboveWavePath, mAboveWavePaint);

}

......

/**

* calculate wave track

* this is why is can wave

* y=Asin(ωx+φ)+k

*/

private void calculatePath() {

mAboveWavePath.reset();

mBlowWavePath.reset();

getWaveOffset();

float y;

mAboveWavePath.moveTo(left, bottom);

// calculate the path by sin

for (float x = 0; x <= mMaxRight; x += X_SPACE) {

y = (float) (mWaveHeight * Math.sin(omega * x + mAboveOffset) + mWaveHeight);

mAboveWavePath.lineTo(x, y);

}

mAboveWavePath.lineTo(right, bottom);

mBlowWavePath.moveTo(left, bottom);

for (float x = 0; x <= mMaxRight; x += X_SPACE) {

y = (float) (mWaveHeight * Math.sin(omega * x + mBlowOffset) + mWaveHeight);

mBlowWavePath.lineTo(x, y);

}

mBlowWavePath.lineTo(right, bottom);

}

@Override

protected void onWindowVisibilityChanged(int visibility) {

super.onWindowVisibilityChanged(visibility);

if (View.GONE == visibility) {

removeCallbacks(mRefreshProgressRunnable);

} else {

removeCallbacks(mRefreshProgressRunnable);

mRefreshProgressRunnable = new RefreshProgressRunnable();

post(mRefreshProgressRunnable);

}

}

@Override

protected void onDetachedFromWindow() {

super.onDetachedFromWindow();

}

@Override

public void onWindowFocusChanged(boolean hasWindowFocus) {

super.onWindowFocusChanged(hasWindowFocus);

if (hasWindowFocus) {

if (mWaveLength == 0) {

startWave();

}

}

}

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

super.onLayout(changed, left, top, right, bottom);

if (mWaveLength==0){

startWave();

}

}

private void startWave() {

if (getWidth() != 0) {

int width = getWidth();

mWaveLength = width * mWaveMultiple;

left = getLeft();

right = getRight();

bottom = getBottom() + 2;

mMaxRight = right + X_SPACE;

omega = PI2 / mWaveLength;

}

}

private void getWaveOffset() {

if (mBlowOffset > Float.MAX_VALUE - 100) {

mBlowOffset = 0;

} else {

mBlowOffset += mWaveHz;

}

if (mAboveOffset > Float.MAX_VALUE - 100) {

mAboveOffset = 0;

} else {

mAboveOffset += mWaveHz;

}

}

private class RefreshProgressRunnable implements Runnable {

public void run() {

synchronized (Wave.this) {

long start = System.currentTimeMillis();

calculatePath();

invalidate();

// update every 16ms

long gap = 16 - (System.currentTimeMillis() - start);

postDelayed(this, gap < 0 ? 0 : gap);

}

}

}

}

Wave的逻辑是这样的:

当窗口可视时,如果View不是GONE则调用post方法后台运行一个RefreshProgressRunnable对象

RefreshProgressRunnable对象则调用calculatePath方法计算要画的路径,然后调用postDelayed方法,在16ms内再次计算路径

calculatePath方法中则根据y=Asin(ωx+φ)+k,x以一定的间隔增大,从左到右的计算函数值并调用path的lineTo方法勾画路径,k则为设置的高度值,是水波的最高值

调用invalidate方法更新视图

onDraw方法中根据路径重画

继承特定的ViewGroup实现自定义View

这里是WaveView,它继承了LinearLayout,里面添加了Wave和Solid两个View,Wave负责水波纹效果,Solid则是水面下。

public class WaveView extends LinearLayout {

......

public WaveView(Context context, AttributeSet attrs) {

super(context, attrs);

setOrientation(VERTICAL);

......

addView(mWave);

addView(mSolid);

setProgress(mProgress);

}

// The height of Wave

public void setProgress(int progress) {

this.mProgress = progress > 100 ? 100 : progress;

computeWaveToTop();

}

@Override

public void onWindowFocusChanged(boolean hasWindowFocus) {

super.onWindowFocusChanged(hasWindowFocus);

if (hasWindowFocus) {

computeWaveToTop();

}

}

private void computeWaveToTop() {

mWaveToTop = (int) (getHeight() * (1f - mProgress / 100f));

ViewGroup.LayoutParams params = mWave.getLayoutParams();

if (params != null) {

((LayoutParams) params).topMargin = mWaveToTop;

}

mWave.setLayoutParams(params);

}

......

}

这个就比较简单了,computeWaveToTop计算wave视图到顶部的距离,以此实现WaveView progress的变化。

总结

上面这个例子实现了自定义View,继承View 重写了onDraw方法,同时继承LinearLayout,通过组合加上多线程的更新,达到了一个良好的效果。这也同时说明了自定义View对我们开发者有多重要,它通过特殊的排列组合能够完成令人眼前一亮的效果,大大拓展了安卓开发界面的效果。

参考资料

个人思考

有时候会觉得遇到了瓶颈,有时候会不知道做什么,这时候就学点新东西吧,看看书吧,这种时候就是我们能够大进步的时候,有瓶颈说明我们有不足,即使无法认清我们的不足之处,我们也可以学点新的东西,把旧知识弄的深入。会有进步的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值