1.标准正余弦水波纹;
2.非标准圆形液柱水波纹;
虽说都是水波纹,但两者在实现上差异是比较大的,一个通过正余弦函数模拟水波纹效果,另外一个会运用到图像的混合模式(PorterDuffXfermode);
先看效果:
自定义View根据实际情况可以选择继承自View、TextView、ImageView或其他
这次的实现我们都选择继承view,在实现的过程中我们需要关注如下几个方法:
1.onMeasure():最先回调,用于控件的测量
2.onSizeChanged():在onMeasure后面回调,可以拿到view的宽高等数据,在横竖屏切换时也会回调
3.onDraw():真正的绘制部分,绘制的代码都写到这里面
既然如此,我们先复写这三个方法,然后来实现如上两个效果;
一:标准正余弦水波纹
这种水波纹可以用具体函数模拟出具体的轨迹,所以思路基本如下:
1.确定水波函数方程
2.根据函数方程得出每一个波纹上点的坐标;
3.将水波进行平移,即将水波上的点不断的移动;
4.不断的重新绘制,生成动态水波纹;
有了上面的思路,我们一步一步进行实现:
正余弦函数方程为:
y = Asin(wx+b)+h ,这个公式里:w影响周期,A影响振幅,h影响y位置,b为初相;
根据上面的方程选取自己觉得中意的波纹效果,确定对应参数的取值;
然后根据确定好的方程得出所有的方程上y的数值,并将所有y值保存在数组里:
// 将周期定为view总宽度
mCycleFactorW = (float) (2 * Math.PI / mTotalWidth)
// 根据view总宽度得出所有对应的y值
for (int i = 0 i < mTotalWidth i++) {
mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y)
}
根据得出的所有y值,则可以在onDraw中通过如下代码绘制两条静态波纹:
for (int i = 0 i < mTotalWidth i++) {
// 减400只是为了控制波纹绘制的y的在屏幕的位置,大家可以改成一个变量,然后动态改变这个变量,从而形成波纹上升下降效果
// 绘制第一条水波纹
canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,
mTotalHeight,
mWavePaint)
// 绘制第二条水波纹
canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,
mTotalHeight,
mWavePaint)
}
这种方式类似于数学里面的细分法,一条波纹,如果横向以一个像素点为单位进行细分,则形成view总宽度条直线,并且每条直线的起点和终点我们都能知道,在此基础上我们只需要循环绘制出所有细分出来的直线(直线都是纵向的),则形成了一条静态的水波纹;
接下来我们让水波纹动起来,之前用了一个数组保存了所有的y值点,有两条水波纹,再利用两个同样大小的数组来保存两条波纹的y值数据,并不断的去改变这两个数组中的数据:
private void resetPositonY() {
// mXOneOffset代表当前第一条水波纹要移动的距离
int yOneInterval = mYPositions.length - mXOneOffset
// 使用System.arraycopy方式重新填充第一条波纹的数据
System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval)
System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset)
int yTwoInterval = mYPositions.length - mXTwoOffset
System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
yTwoInterval)
System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset)
}
如此下来只要不断的改变这两个数组的数据,然后不断刷新,即可生成动态水波纹了;
刷新可以调用invalidate()或postInvalidate(),区别在于后者可以在子线程中更新UI
整体代码如下:
package com.csdn.csdnblog2.ui
import com.csdn.csdnblog2.utils.UiUtils
import android.content.Context
import android.graphics.Canvas
import android.graphics.DrawFilter
import android.graphics.Paint
import android.graphics.Paint.Style
import android.graphics.PaintFlagsDrawFilter
import android.util.AttributeSet
import android.view.View
public class DynamicWave extends View {
// 波纹颜色
private static final int WAVE_PAINT_COLOR = 0x880000aa
// y = Asin(wx+b)+h
private static final float STRETCH_FACTOR_A = 20
private static final int OFFSET_Y = 0
// 第一条水波移动速度
private static final int TRANSLATE_X_SPEED_ONE = 7
// 第二条水波移动速度
private static final int TRANSLATE_X_SPEED_TWO = 5
private float mCycleFactorW
private int mTotalWidth, mTotalHeight
private float[] mYPositions
private float[] mResetOneYPositions
private float[] mResetTwoYPositions
private int mXOffsetSpeedOne
private int mXOffsetSpeedTwo
private int mXOneOffset
private int mXTwoOffset
private Paint mWavePaint
private DrawFilter mDrawFilter
public DynamicWave(Context context, AttributeSet attrs) {
super(context, attrs)
// 将dp转化为px,用于控制不同分辨率上移动速度基本一致
mXOffsetSpeedOne = UiUtils.dipToPx(context, TRANSLATE_X_SPEED_ONE)
mXOffsetSpeedTwo = UiUtils.dipToPx(context, TRANSLATE_X_SPEED_TWO)
// 初始绘制波纹的画笔
mWavePaint = new Paint()
// 去除画笔锯齿
mWavePaint.setAntiAlias(true)
// 设置风格为实线
mWavePaint.setStyle(Style.FILL)
// 设置画笔颜色
mWavePaint.setColor(WAVE_PAINT_COLOR)
mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas)
// 从canvas层面去除绘制时锯齿
canvas.setDrawFilter(mDrawFilter)
resetPositonY()
for (int i = 0 i < mTotalWidth i++) {
// 减400只是为了控制波纹绘制的y的在屏幕的位置,大家可以改成一个变量,然后动态改变这个变量,从而形成波纹上升下降效果
// 绘制第一条水波纹
canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,
mTotalHeight,
mWavePaint)
// 绘制第二条水波纹
canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,
mTotalHeight,
mWavePaint)
}
// 改变两条波纹的移动点
mXOneOffset += mXOffsetSpeedOne
mXTwoOffset += mXOffsetSpeedTwo
// 如果已经移动到结尾处,则重头记录
if (mXOneOffset >= mTotalWidth) {
mXOneOffset = 0
}
if (mXTwoOffset > mTotalWidth) {
mXTwoOffset = 0
}
// 引发view重绘,一般可以考虑延迟20-30ms重绘,空出时间片
postInvalidate()
}
private void resetPositonY() {
// mXOneOffset代表当前第一条水波纹要移动的距离
int yOneInterval = mYPositions.length - mXOneOffset
// 使用System.arraycopy方式重新填充第一条波纹的数据
System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval)
System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset)
int yTwoInterval = mYPositions.length - mXTwoOffset
System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
yTwoInterval)
System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset)
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh)
// 记录下view的宽高
mTotalWidth = w
mTotalHeight = h
// 用于保存原始波纹的y值
mYPositions = new float[mTotalWidth]
// 用于保存波纹一的y值
mResetOneYPositions = new float[mTotalWidth]
// 用于保存波纹二的y值
mResetTwoYPositions = new float[mTotalWidth]
// 将周期定为view总宽度
mCycleFactorW = (float) (2 * Math.PI / mTotalWidth)
// 根据view总宽度得出所有对应的y值
for (int i = 0 i < mTotalWidth i++) {
mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y)
}
}
}
二:非标准圆形液柱水波纹
前面的波形使用函数模拟,这个我们换种方式,采用图进行实现,先用PS整张不像波纹的波纹图;
为了衔接紧密,首尾都比较平,并高度一致;
思路:
1.使用一个圆形图作为遮罩过滤波形图;
2.平移波纹图,即不断改变绘制的波纹图的区域,即srcRect;
3.当一个周期绘制完,则从波纹图的最前面重新计算;
全部代码如下
// 初始化bitmap
private void initBitmap() {
mSrcBitmap = ((BitmapDrawable) getResourceswww.yingtaow.com?zhidaogetDrawable(R.drawable.wave_2000))
.getBitmap()
mMaskBitmap = ((BitmapDrawable) getResources().getDrawable(
R.drawable.circle_500))
.getBitmap()
}
// 初始化画笔paint
private void initPaint() {
mBitmapPaint = new Paint()
// 防抖动
mBitmapPaint.setDither(true)
// 开启图像过滤
mBitmapPaint.setFilterBitmap(true)
mPicPaint = new Paint(Paint.ANTI_ALIAS_FLAG)
mPicPaint.setDither(true)
mPicPaint.setColor(Color.RED)
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh)
mTotalWidth = w
mTotalHeight = h
mCenterX = mTotalWidth / 2
mCenterY = mTotalHeight / 2
mSrcRect = new Rect()
mDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight)
int maskWidth = mMaskBitmap.getWidth()
int maskHeight = mMaskBitmap.getHeight()
mMaskSrcRect = new Rect(0, 0, maskWidth, maskHeight)
mMaskDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight)
}
}
android 自定义圆角ImageView以及锯齿的处理看到很多人开发过程中要使用圆角图片时,解决方法有:
1.重新绘制一张图片
2.通过布局来配置
3.通过重写View来实现
其中1,2在这里就不讲了,重点讲讲方法三的实现。
实现一:通过截取画布一个圆形区域与图片的相交部分进行绘制,缺点:锯齿明显,设置Paint,Canvas抗锯齿无效。
package com.open.circleimageview.widget
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PaintFlagsDrawFilter
import android.graphics.Path
import android.graphics.Rect
import android.graphics.Region
import android.util.AttributeSet
import android.view.View
public class CircleImageViewA extends View {
public CircleImageViewA(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle)
}
public CircleImageViewA(Context context, AttributeSet attrs) {
super(context, attrs)
}
public CircleImageViewA(Context context) {
super(context)
}
private Bitmap bitmap
private Rect bitmapRect=new Rect()
private PaintFlagsDrawFilter pdf=new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG)
private Paint paint = new Paint()
{
paint.setStyle(Paint.Style.STROKE)
paint.setFlags(Paint.ANTI_ALIAS_FLAG)
paint.setAntiAlias(true)// 设置画笔的锯齿效果。 true是去除,大家一看效果就明白了
}
private Path mPath=new Path()
public void setImageBitmap(Bitmap bitmap)
{
this.bitmap=bitmap
}
@Override
protected void onDraw(Canvas canvas) {
if(null==bitmap)
{
return
}
bitmapRect.set(0, 0, getWidth(), getHeight())
canvas.save()
canvas.setDrawFilter(pdf)
mPath.reset()
canvas.clipPath(mPath)// makes the clip empty
mPath.addCircle(getWidth()/2, getWidth()/2, getHeight()/2, Path.Direction.CCW)
canvas.clipPath(mPath, Region.Op.REPLACE)
canvas.drawBitmap(bitmap, null, bitmapRect, paint)
canvas.restore()
}
}
实现二:通过PorterDuffXfermode 方式(注意要设置硬件加速,否则部分机子无效),优点:锯齿基本没有
package com.open.circleimageview.widget
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PaintFlagsDrawFilter
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
public class CircleImageViewB extends View {
public CircleImageViewB(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle)
init()
}
public CircleImageViewB(Context context, AttributeSet attrs) {
super(context, attrs)
init()
}
public CircleImageViewB(Context context) {
super(context)
init()
}
private Bitmap bitmap
private Rect bitmapRect=new Rect()
private PaintFlagsDrawFilter pdf=new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG)
private Paint paint = new Paint()
{
paint.setStyle(Paint.Style.STROKE)
paint.setFlags(Paint.ANTI_ALIAS_FLAG)
paint.setAntiAlias(true)// 设置画笔的锯齿效果。 true是去除,大家一看效果就明白了
}
private Bitmap mDstB=null
private PorterDuffXfermode xfermode=new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)
private void init()
{
try {
if(android.os.Build.VERSION.SDK_INT>=11)
{
setLayerType(LAYER_TYPE_SOFTWARE, null)
}
} catch (Exception e) {
e.printStackTrace()
}
}
public void setImageBitmap(Bitmap bitmap)
{
this.bitmap=bitmap
}
private Bitmap makeDst(int w, int h)
{
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
Canvas c = new Canvas(bm)
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG)
p.setColor(Color.parseColor("#ffffffff"))
c.drawOval(new RectF(0, 0, w, h), p)
return bm
}
@Override
protected void onDraw(Canvas canvas) {
if(null==bitmap)
{
return
}
if(null==mDstB)
{
mDstB=makeDst(getWidth(), getHeight())
}
bitmapRect.set(0, 0, getWidth(), getHeight())
canvas.save()
canvas.setDrawFilter(pdf)
canvas.drawBitmap(mDstB, 0, 0, paint)
paint.setXfermode(xfermode)
canvas.drawBitmap(bitmap, null, bitmapRect, paint)
paint.setXfermode(null)
canvas.restore()
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)