(十二)——仿支付宝咻一咻功能实现波纹扩散特效,精细小巧的View

最后更新于:2022-04-01 09:36:33

# Android特效专辑(十二)——仿支付宝咻一咻功能实现波纹扩散特效,精细小巧的View > 先来看看这个效果 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1bb0a55.jpg) > 这是我的在Only上添加的效果,说实话,Only现在都还只是半成品,台面都上不了,怪自己技术不行,也太懒了  > *PS:这个view也是我模仿了人家的效果,参考了人家的思路写的,不是纯手撸,罪过罪过,网上应该也能找到很多这样的效果,我只是加入了一些自己的需求在里面* 我么新建一个工程——Whew ## RoundImageView > 这个之前讲过,网上 的粒子,把头像变成圆形的,这里就不多说了,直接撸代码吧! ~~~ package com.lgl.whew; /** * 圆形头像 * Created by LGL on 2016/1/12. */ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.NinePatchDrawable; import android.util.AttributeSet; import android.widget.ImageView; /** * 圆形ImageView,可设置最多两个宽度不同且颜色不同的圆形边框。 * * 设置颜色在xml布局文件中由自定义属性配置参数指定 */ public class RoundImageView extends ImageView { private int mBorderThickness = 0; private Context mContext; private int defaultColor = 0xFFFFFFFF; // 如果只有其中一个有值,则只画一个圆形边框 private int mBorderOutsideColor = 0; private int mBorderInsideColor = 0; // 控件默认长、宽 private int defaultWidth = 0; private int defaultHeight = 0; public RoundImageView(Context context) { super(context); mContext = context; } public RoundImageView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; setCustomAttributes(attrs); } public RoundImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; setCustomAttributes(attrs); } private void setCustomAttributes(AttributeSet attrs) { TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.roundedimageview); mBorderThickness = a.getDimensionPixelSize( R.styleable.roundedimageview_border_thickness, 0); mBorderOutsideColor = a .getColor(R.styleable.roundedimageview_border_outside_color, defaultColor); mBorderInsideColor = a.getColor( R.styleable.roundedimageview_border_inside_color, defaultColor); } @Override protected void onDraw(Canvas canvas) { Drawable drawable = getDrawable(); if (drawable == null) { return; } if (getWidth() == 0 || getHeight() == 0) { return; } this.measure(0, 0); if (drawable.getClass() == NinePatchDrawable.class) return; Bitmap b = ((BitmapDrawable) drawable).getBitmap(); Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true); if (defaultWidth == 0) { defaultWidth = getWidth(); } if (defaultHeight == 0) { defaultHeight = getHeight(); } int radius = 0; if (mBorderInsideColor != defaultColor && mBorderOutsideColor != defaultColor) {// 定义画两个边框,分别为外圆边框和内圆边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - 2 * mBorderThickness; // 画内圆 drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderInsideColor); // 画外圆 drawCircleBorder(canvas, radius + mBorderThickness + mBorderThickness / 2, mBorderOutsideColor); } else if (mBorderInsideColor != defaultColor && mBorderOutsideColor == defaultColor) {// 定义画一个边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - mBorderThickness; drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderInsideColor); } else if (mBorderInsideColor == defaultColor && mBorderOutsideColor != defaultColor) {// 定义画一个边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - mBorderThickness; drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderOutsideColor); } else {// 没有边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2; } Bitmap roundBitmap = getCroppedRoundBitmap(bitmap, radius); canvas.drawBitmap(roundBitmap, defaultWidth / 2 - radius, defaultHeight / 2 - radius, null); } /** * 获取裁剪后的圆形图片 */ public Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) { Bitmap scaledSrcBmp; int diameter = radius * 2; // 为了防止宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片 int bmpWidth = bmp.getWidth(); int bmpHeight = bmp.getHeight(); int squareWidth = 0, squareHeight = 0; int x = 0, y = 0; Bitmap squareBitmap; if (bmpHeight > bmpWidth) {// 高大于宽 squareWidth = squareHeight = bmpWidth; x = 0; y = (bmpHeight - bmpWidth) / 2; // 截取正方形图片 squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else if (bmpHeight < bmpWidth) {// 宽大于高 squareWidth = squareHeight = bmpHeight; x = (bmpWidth - bmpHeight) / 2; y = 0; squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else { squareBitmap = bmp; } if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) { scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true); } else { scaledSrcBmp = squareBitmap; } Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); Paint paint = new Paint(); Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight()); paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); canvas.drawARGB(0, 0, 0, 0); canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(scaledSrcBmp, rect, rect, paint); bmp = null; squareBitmap = null; scaledSrcBmp = null; return output; } /** * 边缘画圆 */ private void drawCircleBorder(Canvas canvas, int radius, int color) { Paint paint = new Paint(); /* 去锯齿 */ paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); paint.setColor(color); /* 设置paint的 style 为STROKE:空心 */ paint.setStyle(Paint.Style.STROKE); /* 设置paint的外框宽度 */ paint.setStrokeWidth(mBorderThickness); canvas.drawCircle(defaultWidth / 2, defaultHeight / 2, radius, paint); } } ~~~ > 这里值得注意的是,要使用这个必须自定义一些属性,我们在values下新建一个attr.xml ~~~ <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="roundedimageview"> <attr name="border_thickness" format="dimension" /> <attr name="border_inside_color" format="color" /> <attr name="border_outside_color" format="color"></attr> </declare-styleable> </resources> ~~~ > 然后在xml文件中引入命名空间 ~~~ xmlns:imagecontrol="http://schemas.android.com/apk/res-auto" ~~~ > 我们直接看layout_mian.xml吧 ## layout_mian.xml > 就一些布局咯 ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:imagecontrol="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <com.lgl.whew.WhewView android:id="@+id/wv" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.lgl.whew.RoundImageView android:id="@+id/my_photo" android:layout_width="100dp" android:layout_height="100dp" android:layout_centerInParent="true" android:src="@drawable/myphoto" imagecontrol:border_inside_color="#bc0978" imagecontrol:border_outside_color="#ba3456" imagecontrol:border_thickness="1dp" /> </RelativeLayout> </LinearLayout> ~~~ > 这样你就可以使用圆形图片了,我们接下来看波纹的绘制 ## WhewView ~~~ package com.lgl.whew; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; /** * 模仿咻一咻 * * @author LGL * */ public class WhewView extends View { private Paint paint; private int maxWidth = 255; // 是否运行 private boolean isStarting = false; private List<String> alphaList = new ArrayList<String>(); private List<String> startWidthList = new ArrayList<String>(); public WhewView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // TODO Auto-generated constructor stub init(); } public WhewView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub init(); } public WhewView(Context context) { super(context); // TODO Auto-generated constructor stub init(); } private void init() { paint = new Paint(); // 设置博文的颜色 paint.setColor(0x0059ccf5); alphaList.add("255");// 圆心的不透明度 startWidthList.add("0"); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); setBackgroundColor(Color.TRANSPARENT);// 颜色:完全透明 // 依次绘制 同心圆 for (int i = 0; i < alphaList.size(); i++) { int alpha = Integer.parseInt(alphaList.get(i)); // 圆半径 int startWidth = Integer.parseInt(startWidthList.get(i)); paint.setAlpha(alpha); // 这个半径决定你想要多大的扩散面积 canvas.drawCircle(getWidth() / 2, getHeight() / 2, startWidth + 50, paint); // 同心圆扩散 if (isStarting && alpha > 0 && startWidth < maxWidth) { alphaList.set(i, (alpha - 1) + ""); startWidthList.set(i, (startWidth + 1) + ""); } } if (isStarting && Integer .parseInt(startWidthList.get(startWidthList.size() - 1)) == maxWidth / 5) { alphaList.add("255"); startWidthList.add("0"); } // 同心圆数量达到10个,删除最外层圆 if (isStarting && startWidthList.size() == 10) { startWidthList.remove(0); alphaList.remove(0); } // 刷新界面 invalidate(); } // 执行动画 public void start() { isStarting = true; } // 停止动画 public void stop() { isStarting = false; } // 判断是都在不在执行 public boolean isStarting() { return isStarting; } } ~~~ > 这里我们看到,对外有几个方法,一个开始动画,一个停止动画,一个检测是否正在运行 ## MainActivity > 这里就是我们的需求了,我反编译了一下支付宝的APK,并没有找到他的咻一咻的音效,就在他的raw目录下随便找了一个,我们现在是需要这样一个需求 * 点击图片执行动画,并且每隔五分钟响一次 * 再次点击图片,停止动画,停止音效 我们先新建一个raw文件夹把音效拷贝进去吧 ~~~ package com.lgl.whew; import android.app.Activity; import android.media.AudioManager; import android.media.SoundPool; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.view.View.OnClickListener; public class MainActivity extends Activity { private WhewView wv; private RoundImageView my_photo; private static final int Nou = 1; // 声明一个SoundPool private SoundPool sp; // 定义一个整型用load();来设置suondIDf private int music; private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { if (msg.what == Nou) { // 每隔10s响一次 handler.sendEmptyMessageDelayed(Nou, 5000); sp.play(music, 1, 1, 0, 0, 1); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { // 第一个参数为同时播放数据流的最大个数,第二数据流类型,第三为声音质量 sp = new SoundPool(10, AudioManager.STREAM_SYSTEM, 5); // 把你的声音素材放到res/raw里,第2个参数即为资源文件,第3个为音乐的优先级 music = sp.load(this, R.raw.hongbao_gq, 1); wv = (WhewView) findViewById(R.id.wv); my_photo = (RoundImageView) findViewById(R.id.my_photo); my_photo.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if(wv.isStarting()){ //如果动画正在运行就停止,否则就继续执行 wv.stop(); //结束进程 handler.removeMessages(Nou); }else{ // 执行动画 wv.start(); handler.sendEmptyMessage(Nou); } } }); } } ~~~ > 相信这里的逻辑不是很难吧,对了,我们在结束activity的时候也是要销毁这个进程的,不然…你懂的 ~~~ @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); handler.removeMessages(Nou); } ~~~ 我们运行一下,想听效果的可以下载Demo运行一下,我们这里做一个简单的演示 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1bdcdf9.jpg) ## Demo下载地址:[http://download.csdn.net/detail/qq_26787115/9437957](http://download.csdn.net/detail/qq_26787115/9437957)
';

(十一)——仿水波纹流量球进度条控制器,实现高端大气的主流特效

最后更新于:2022-04-01 09:36:31

# Android特效专辑(十一)——仿水波纹流球进度条控制器,实现高端大气的主流特效 * * * > 今天看到一个效果挺不错的,就模仿了下来,加上了一些自己想要的效果,感觉还不错的样子,所以就分享出来了,话不多说,上图 ## 截图 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1b0f569.jpg) ## CircleView > 这里主要是实现中心圆以及水波特效 ~~~ package com.lgl.circleview; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.view.View; import android.widget.ProgressBar; /** * 水波圆 * * @author lgl * */ public class CircleView extends View { private Context mContext; private int mScreenWidth; private int mScreenHeight; private Paint mRingPaint; private Paint mCirclePaint; private Paint mWavePaint; private Paint linePaint; private Paint flowPaint; private Paint leftPaint; private int mRingSTROKEWidth = 15; private int mCircleSTROKEWidth = 2; private int mLineSTROKEWidth = 1; private int mCircleColor = Color.WHITE; private int mRingColor = Color.WHITE; private int mWaveColor = Color.WHITE; private Handler mHandler; private long c = 0L; private boolean mStarted = false; private final float f = 0.033F; private int mAlpha = 50;// 透明度 private float mAmplitude = 10.0F; // 振幅 private float mWaterLevel = 0.5F;// 水高(0~1) private Path mPath; // 绘制文字显示在圆形中间,只是我没有设置,我觉得写在布局上也挺好的 private String flowNum = ""; private String flowLeft = "还剩余"; /** * @param context */ public CircleView(Context context) { super(context); // TODO Auto-generated constructor stub mContext = context; init(mContext); } /** * @param context * @param attrs */ public CircleView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub mContext = context; init(mContext); } /** * @param context * @param attrs * @param defStyleAttr */ public CircleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // TODO Auto-generated constructor stub mContext = context; init(mContext); } public void setmWaterLevel(float mWaterLevel) { this.mWaterLevel = mWaterLevel; } private void init(Context context) { mRingPaint = new Paint(); mRingPaint.setColor(mRingColor); mRingPaint.setAlpha(50); mRingPaint.setStyle(Paint.Style.STROKE); mRingPaint.setAntiAlias(true); mRingPaint.setStrokeWidth(mRingSTROKEWidth); mCirclePaint = new Paint(); mCirclePaint.setColor(mCircleColor); mCirclePaint.setStyle(Paint.Style.STROKE); mCirclePaint.setAntiAlias(true); mCirclePaint.setStrokeWidth(mCircleSTROKEWidth); linePaint = new Paint(); linePaint.setColor(mCircleColor); linePaint.setStyle(Paint.Style.STROKE); linePaint.setAntiAlias(true); linePaint.setStrokeWidth(mLineSTROKEWidth); flowPaint = new Paint(); flowPaint.setColor(mCircleColor); flowPaint.setStyle(Paint.Style.FILL); flowPaint.setAntiAlias(true); flowPaint.setTextSize(36); leftPaint = new Paint(); leftPaint.setColor(mCircleColor); leftPaint.setStyle(Paint.Style.FILL); leftPaint.setAntiAlias(true); leftPaint.setTextSize(36); mWavePaint = new Paint(); mWavePaint.setStrokeWidth(1.0F); mWavePaint.setColor(mWaveColor); mWavePaint.setAlpha(mAlpha); mPath = new Path(); mHandler = new Handler() { @Override public void handleMessage(android.os.Message msg) { if (msg.what == 0) { invalidate(); if (mStarted) { // 不断发消息给自己,使自己不断被重绘 mHandler.sendEmptyMessageDelayed(0, 60L); } } } }; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = measure(widthMeasureSpec, true); int height = measure(heightMeasureSpec, false); if (width < height) { setMeasuredDimension(width, width); } else { setMeasuredDimension(height, height); } } /** * @category 测量 * @param measureSpec * @param isWidth * @return */ private int measure(int measureSpec, boolean isWidth) { int result; int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom(); if (mode == MeasureSpec.EXACTLY) { result = size; } else { result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight(); result += padding; if (mode == MeasureSpec.AT_MOST) { if (isWidth) { result = Math.max(result, size); } else { result = Math.min(result, size); } } } return result; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // TODO Auto-generated method stub super.onSizeChanged(w, h, oldw, oldh); mScreenWidth = w; mScreenHeight = h; } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); // 得到控件的宽高 int width = getWidth(); int height = getHeight(); setBackgroundColor(mContext.getResources().getColor(R.color.main_bg)); // 计算当前油量线和水平中线的距离 float centerOffset = Math.abs(mScreenWidth / 2 * mWaterLevel - mScreenWidth / 4); // 计算油量线和与水平中线的角度 float horiAngle = (float) (Math.asin(centerOffset / (mScreenWidth / 4)) * 180 / Math.PI); // 扇形的起始角度和扫过角度 float startAngle, sweepAngle; if (mWaterLevel > 0.5F) { startAngle = 360F - horiAngle; sweepAngle = 180F + 2 * horiAngle; } else { startAngle = horiAngle; sweepAngle = 180F - 2 * horiAngle; } canvas.drawLine(mScreenWidth * 3 / 8, mScreenHeight * 5 / 8, mScreenWidth * 5 / 8, mScreenHeight * 5 / 8, linePaint); float num = flowPaint.measureText(flowNum); canvas.drawText(flowNum, mScreenWidth * 4 / 8 - num / 2, mScreenHeight * 4 / 8, flowPaint); float left = leftPaint.measureText(flowLeft); canvas.drawText(flowLeft, mScreenWidth * 4 / 8 - left / 2, mScreenHeight * 3 / 8, leftPaint); // 如果未开始(未调用startWave方法),绘制一个扇形 if ((!mStarted) || (mScreenWidth == 0) || (mScreenHeight == 0)) { // 绘制,即水面静止时的高度 RectF oval = new RectF(mScreenWidth / 4, mScreenHeight / 4, mScreenWidth * 3 / 4, mScreenHeight * 3 / 4); canvas.drawArc(oval, startAngle, sweepAngle, false, mWavePaint); return; } // 绘制,即水面静止时的高度 // 绘制,即水面静止时的高度 RectF oval = new RectF(mScreenWidth / 4, mScreenHeight / 4, mScreenWidth * 3 / 4, mScreenHeight * 3 / 4); canvas.drawArc(oval, startAngle, sweepAngle, false, mWavePaint); if (this.c >= 8388607L) { this.c = 0L; } // 每次onDraw时c都会自增 c = (1L + c); float f1 = mScreenHeight * (1.0F - (0.25F + mWaterLevel / 2)) - mAmplitude; // 当前油量线的长度 float waveWidth = (float) Math.sqrt(mScreenWidth * mScreenWidth / 16 - centerOffset * centerOffset); // 与圆半径的偏移量 float offsetWidth = mScreenWidth / 4 - waveWidth; int top = (int) (f1 + mAmplitude); mPath.reset(); // 起始振动X坐标,结束振动X坐标 int startX, endX; if (mWaterLevel > 0.50F) { startX = (int) (mScreenWidth / 4 + offsetWidth); endX = (int) (mScreenWidth / 2 + mScreenWidth / 4 - offsetWidth); } else { startX = (int) (mScreenWidth / 4 + offsetWidth - mAmplitude); endX = (int) (mScreenWidth / 2 + mScreenWidth / 4 - offsetWidth + mAmplitude); } // 波浪效果 while (startX < endX) { int startY = (int) (f1 - mAmplitude * Math.sin(Math.PI * (2.0F * (startX + this.c * width * this.f)) / width)); canvas.drawLine(startX, startY, startX, top, mWavePaint); startX++; } canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2, mScreenWidth / 4 + mRingSTROKEWidth / 2, mRingPaint); canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2, mScreenWidth / 4, mCirclePaint); canvas.restore(); } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.progress = (int) c; return ss; } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); c = ss.progress; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); // 关闭硬件加速,防止异常unsupported operation exception this.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); } /** * @category 开始波动 */ public void startWave() { if (!mStarted) { this.c = 0L; mStarted = true; this.mHandler.sendEmptyMessage(0); } } /** * @category 停止波动 */ public void stopWave() { if (mStarted) { this.c = 0L; mStarted = false; this.mHandler.removeMessages(0); } } /** * @category 保存状态 */ static class SavedState extends BaseSavedState { int progress; /** * Constructor called from {@link ProgressBar#onSaveInstanceState()} */ SavedState(Parcelable superState) { super(superState); } /** * Constructor called from {@link #CREATOR} */ private SavedState(Parcel in) { super(in); progress = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(progress); } public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } } ~~~ > 我们运行一下 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1b48542.jpg) > 其实他是十分的空旷的,所以也值得我们去定制,我们在中间加个流量显示,再加个进度条 ## activity_main.xml ~~~ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/main_bg" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="10dp" android:text="流量" android:textColor="@android:color/white" android:textSize="18sp" /> <com.lgl.circleview.CircleView android:id="@+id/wave_view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_centerInParent="true" /> <TextView android:id="@+id/power" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="@android:color/white" /> <SeekBar android:id="@+id/seekBar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginBottom="150dp" /> </RelativeLayout> ~~~ > 我们要实现这个,就要调用它的初始化以及start方法 ~~~ mCircleView = (CircleView) findViewById(R.id.wave_view); // 设置多高,float,0.1-1F mCircleView.setmWaterLevel(0.1F); // 开始执行 mCircleView.startWave(); ~~~ > 别忘了activity销毁的时候把它回收哦 ~~~ @Override protected void onDestroy() { // TODO Auto-generated method stub mCircleView.stopWave(); mCircleView = null; super.onDestroy(); } ~~~ > 我们再运行一遍 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1b628a0.jpg) > 但是我们要怎么让水波纹随着进度条一起上升下降尼?,这里我们就要用到我们刚才写的SeekBar了,我们实现它的setOnSeekBarChangeListener来监听,这样我们就要复写他的三个方法,这里我们只要用到一个 ~~~ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { //跟随进度条滚动 mCircleView.setmWaterLevel((float) progress / 100); } ~~~ > 这里,我们要这样算的,我们设置高度的单位是float,也就是从0-1F,而我们的进度是int progress,从0-100,我们就要用(float) progress / 100)并且强转来得到单位,好了,我们现在水波纹的高度就是随着我们的进度条一起变化了,我们再来运行一下 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1b80a19.jpg) > 好的,这样的话,我们就只剩下一个了,就是让大小随着我们的进度条变化了,这里我们因为更新UI不能再主线程中操作,所以我们需要用到我们的老伙计Handler了,但是用到handler还不够,我们的进度条数值也是在内部类里面,所以这里我们需要用到Handler来传值了,这里我们用的是Bundle,我们还是在onProgressChanged方法中操作了 ~~~ //创建一个消息 Message message = new Message(); Bundle bundle = new Bundle(); //put一个int值 bundle.putInt("progress", progress); //装载 message.setData(bundle); //发送消息 handler.sendMessage(message); //创建表示 message.what = 1; ~~~ > 消息发送过去了,我们就在前面写个Handler去接收就是了 ~~~ private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { if (msg.what == 1) { int num = msg.getData().getInt("progress"); Log.i("num", num + ""); power.setText((float) num / 100 * max + "M/" + max + "M"); } } }; ~~~ > 这里的计算公式尼,是当前的数值/100得到百分比再去*最大值。我们现在可以完整的运行一下了,其实和最上面运行的图片是一样的 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1b0f569.jpg) ## MainActivity ~~~ package com.lgl.circleview; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.widget.SeekBar; import android.widget.TextView; public class MainActivity extends Activity { private CircleView mCircleView; private SeekBar mSeekBar; private TextView power; private int max = 1024; private int min = 102; private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { if (msg.what == 1) { int num = msg.getData().getInt("progress"); Log.i("num", num + ""); power.setText((float) num / 100 * max + "M/" + max + "M"); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getActionBar().hide(); setContentView(R.layout.activity_main); power = (TextView) findViewById(R.id.power); power.setText(min + "M/" + max + "M"); mCircleView = (CircleView) findViewById(R.id.wave_view); // 设置多高,float,0.1-1F mCircleView.setmWaterLevel(0.1F); // 开始执行 mCircleView.startWave(); mSeekBar = (SeekBar) findViewById(R.id.seekBar); mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { mCircleView.setmWaterLevel((float) progress / 100); // 创建一个消息 Message message = new Message(); Bundle bundle = new Bundle(); // put一个int值 bundle.putInt("progress", progress); // 装载 message.setData(bundle); // 发送消息 handler.sendMessage(message); // 创建表示 message.what = 1; } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); } @Override protected void onDestroy() { // TODO Auto-generated method stub mCircleView.stopWave(); mCircleView = null; super.onDestroy(); } } ~~~ ## Demo下载地址:[http://download.csdn.net/detail/qq_26787115/9435934](http://download.csdn.net/detail/qq_26787115/9435934)
';

(十)——点击水波纹效果实现,逻辑清晰实现简单

最后更新于:2022-04-01 09:36:28

# Android特效专辑(十)——点击水波纹效果实现,逻辑清晰实现简单 > 这次做的东西呢,和上篇有点类似,就是用比较简单的逻辑思路去实现一些比较好玩的特效,最近也是比较忙,所以博客更新的速度还得看时间去推演,但是也能保证一周三更的样子,现在也还是以小功能,或者说是一些小入门级别的博客为主,我也不算是什么很厉害的人,很多细节的支持处理的仍然还是不到位,所以也是一直在弥补,话不多说,来看看今天的效果 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1ae7699.jpg) > 实现起来很简单吧,那我们就来看一下他是怎么实现的咯! ## OnclickRuning ~~~ package com.lgl.onclickruning; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; /** * 点击水波纹涟漪动画效果 * * @author LGL * */ public class Runing extends View { // 画笔 private Paint mPaint; // 内圆宽度 private int strokeWidth; // 圆心x private int cx; // 圆心y private int cy; // 半径 private int radius; // Handler消息 private static final int FLUSH = 0; public Runing(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { // 初始化画笔 mPaint = new Paint(); // 抗锯齿 mPaint.setAntiAlias(true); // 设置颜色 mPaint.setColor(Color.BLUE); // 设置空心 mPaint.setStyle(Style.STROKE); // 设置内圆的宽度 mPaint.setStrokeWidth(strokeWidth); // 设置透明度 0-255 mPaint.setAlpha(255); // 初始值 strokeWidth = 0; radius = 0; } /** * 绘制 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 绘制圆环 canvas.drawCircle(cx, cy, radius, mPaint); } /** * 触摸事件 */ @Override public boolean onTouchEvent(MotionEvent event) { // 判断手势按下和抬起 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: /** * 按下去开始画圆,也就是起涟漪,所以我们首先得获取到按下的坐标,事实上,我们要做这样操作的开发,都必须先提前拿到该有的坐标 */ cx = (int) event.getX(); cy = (int) event.getY(); Log.i("坐标", "圆心x:" + cx + "圆心y:" + cy); break; } // 初始化 init(); // 发送 handler.sendEmptyMessage(FLUSH); return true; } /** * 刷新状态 */ private void flush() { // 半径每次+10 radius += 10; // 线条的宽度每次都是半径的四分之一 strokeWidth = radius / 4; // 重新设置给画笔 mPaint.setStrokeWidth(strokeWidth); // 颜色渐变,每次减少20的色值 int nextAlpha = mPaint.getAlpha() - 20; // 避免等于负数 if (nextAlpha < 20) { // 直接设置为透明 nextAlpha = 0; } // 继续重新设置给画笔 mPaint.setAlpha(nextAlpha); } private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case FLUSH: // 更改参数状态 flush(); // 刷新 执行我们的绘制方法 invalidate(); // 继续验证透明度,只要不为0就一直发送,直到透明 if (mPaint.getAlpha() != 0) { handler.sendEmptyMessageDelayed(FLUSH, 100); } break; } } }; } ~~~ ## layout_main.xml ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.lgl.onclickruning.Runing android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> ~~~ > 代码其实很简单,我们简单的逻辑就可以实现了,但是别小看了这个思维,你可以用这个逻辑去实现更多有趣的特效,这里就期待你的挖掘了,嘻嘻 ## Demo下载地址:[http://download.csdn.net/detail/qq_26787115/9433534](http://download.csdn.net/detail/qq_26787115/9433534)
';

(九)——仿微信雷达搜索好友特效,逻辑清晰实现简单

最后更新于:2022-04-01 09:36:26

# Android特效专辑(九)——仿微信雷达搜索好友特效,逻辑清晰实现简单 > 不知不觉这个春节也已经过完了,遗憾家里没网,没能及时给大家送上祝福,今天回到深圳,明天就要上班了,小伙伴们是不是和我一样呢?今天讲的是一个大家都见过的动画,雷达搜索好友嘛,原理也十分的简单,你看完我的分析,也会觉得很简单了,国际惯例,无图无真相,我们先看看效果图,对了,真  > 测试机送人了,所讲这段时间应该一直用模拟器显示吧! ## 截图 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1a5da6a.jpg) > 这个界面相信大家都认识,我们来说下原理,其实就三层 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1aa26e7.jpg) > 中间是一张图片,然后画四个圆,这个应该简单吧,不会的可以看下[Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解](http://blog.csdn.net/qq_26787115/article/details/50466655),然后最上面就是一个渐变的圆了,这个圆我们只要让他不停的旋转就可以了,那我们新建一个工程——RadarSearch ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1abb2ac.jpg) ## layou_main.xml ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/photo" > <com.lgl.radarsearch.RadarView android:layout_width="match_parent" android:layout_height="match_parent" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/circle_photo" /> </RelativeLayout> ~~~ > 我们新建一个RadarView ## RadarView ~~~ package com.lgl.radarsearch; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Shader; import android.graphics.SweepGradient; import android.os.Handler; import android.util.AttributeSet; import android.view.View; /** * 雷达搜索 * * @author LGL * */ public class RadarView extends View { /** * 思路:我们首先初始化画笔,并且获取到控件的宽高,在onMeasure()中设置铺满,然后在onDraw()方法中绘制四个静态圆和一个渐变圆, * 我们通过Matrix矩阵来让他不停的旋转就达到我们想要的效果了 */ private Paint mPaintLine, mPaintCircle; private int w, h; // 动画 private Matrix matrix; // 旋转角度 private int start; // Handler定时动画 private Handler handler = new Handler(); private Runnable run = new Runnable() { @Override public void run() { start = start + 1; matrix = new Matrix(); // 参数:旋转角度,围绕点坐标的x,y坐标点 matrix.postRotate(start, w / 2, h / 2); // 刷新重绘 RadarView.this.invalidate(); // 继续循环 handler.postDelayed(run, 60); } }; public RadarView(Context context, AttributeSet attrs) { super(context, attrs); initView(); // 获取高宽 w = context.getResources().getDisplayMetrics().widthPixels; h = context.getResources().getDisplayMetrics().heightPixels; // 一致旋转 handler.post(run); } private void initView() { mPaintLine = new Paint(); mPaintLine.setColor(Color.WHITE); mPaintLine.setAntiAlias(true); mPaintLine.setStyle(Style.STROKE); mPaintCircle = new Paint(); mPaintCircle.setColor(Color.RED); mPaintCircle.setAntiAlias(true); matrix = new Matrix(); } /** * 测量 * * @author LGL */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 设置铺满 setMeasuredDimension(w, h); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 画四个圆形 canvas.drawCircle(w / 2, h / 2, w / 2, mPaintLine); canvas.drawCircle(w / 2, h / 2, w / 3, mPaintLine); canvas.drawCircle(w / 2, h / 2, w * 7 / 10, mPaintLine); canvas.drawCircle(w / 2, h / 2, w / 4, mPaintLine); // 绘制渐变圆 Shader mShader = new SweepGradient(w / 2, h / 2, Color.TRANSPARENT, Color.parseColor("#AAAAAAAA")); // 绘制时渐变 mPaintCircle.setShader(mShader); // 增加旋转动画,使用矩阵实现 canvas.concat(matrix); // 前置动画 canvas.drawCircle(w / 2, h / 2, w * 7 / 10, mPaintCircle); } } ~~~ ## Demo下载地址:[http://download.csdn.net/detail/qq_26787115/9430942](http://download.csdn.net/detail/qq_26787115/9430942)
';

(八)——实现心型起泡飞舞的特效,让你的APP瞬间暖心

最后更新于:2022-04-01 09:36:24

# Android特效专辑(八)——实现心型起泡飞舞的特效,让你的APP瞬间暖心 * * * > 马上也要放年假了,家里估计会没网,更完这篇的话,可能要到年后了,不过在此期间会把更新内容都保存在本地,这样有网就可以发表了,也是极好的,今天说的这个特效,原本是Only上的一个小彩蛋的,我们来看看图片 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c199b13c.jpg) > 只要我点击了Only这个字,下面就开始上升起起泡了,这个实现起来其实就是一个欲盖弥彰的动画而已,准备好三张颜色不一样的心型图片咯,这样的话,我们就开始动手来写一写吧!  > **首先新建一个工程——HeartFaom**  > 准备工作就是准备图片咯 ## BezierEvaluator ~~~ 单位转换以及计算轨迹 ~~~ ~~~ package com.lgl.heartfaom; import android.animation.TypeEvaluator; import android.graphics.PointF; public class BezierEvaluator implements TypeEvaluator<PointF> { private PointF pointF1; private PointF pointF2; public BezierEvaluator(PointF pointF1, PointF pointF2) { this.pointF1 = pointF1; this.pointF2 = pointF2; } @Override public PointF evaluate(float time, PointF startValue, PointF endValue) { float timeLeft = 1.0f - time; PointF point = new PointF();// 结果 point.x = timeLeft * timeLeft * timeLeft * (startValue.x) + 3 * timeLeft * timeLeft * time * (pointF1.x) + 3 * timeLeft * time * time * (pointF2.x) + time * time * time * (endValue.x); point.y = timeLeft * timeLeft * timeLeft * (startValue.y) + 3 * timeLeft * timeLeft * time * (pointF1.y) + 3 * timeLeft * time * time * (pointF2.y) + time * time * time * (endValue.y); return point; } } ~~~ ## PeriscopeLayout ~~~ 贝塞尔曲线的计算以及气泡的实现 ~~~ ~~~ package com.lgl.heartfaom; import java.util.Random; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.Context; import android.graphics.PointF; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.widget.ImageView; import android.widget.RelativeLayout; public class PeriscopeLayout extends RelativeLayout { private Interpolator line = new LinearInterpolator();// 线性 private Interpolator acc = new AccelerateInterpolator();// 加速 private Interpolator dce = new DecelerateInterpolator();// 减速 private Interpolator accdec = new AccelerateDecelerateInterpolator();// 先加速后减速 private Interpolator[] interpolators; private int mHeight; private int mWidth; private LayoutParams lp; private Drawable[] drawables; private Random random = new Random(); private int dHeight; private int dWidth; public PeriscopeLayout(Context context) { super(context); init(); } public PeriscopeLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public PeriscopeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public PeriscopeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { // 初始化显示的图片 drawables = new Drawable[3]; Drawable red = getResources().getDrawable(R.drawable.pl_red); Drawable yellow = getResources().getDrawable(R.drawable.pl_yellow); Drawable blue = getResources().getDrawable(R.drawable.pl_blue); drawables[0] = red; drawables[1] = yellow; drawables[2] = blue; // 获取图的宽高 用于后面的计算 // 注意 我这里3张图片的大小都是一样的,所以我只取了一个 dHeight = red.getIntrinsicHeight(); dWidth = red.getIntrinsicWidth(); // 底部 并且 水平居中 lp = new LayoutParams(dWidth, dHeight); lp.addRule(CENTER_HORIZONTAL, TRUE);// 这里的TRUE 要注意 不是true lp.addRule(ALIGN_PARENT_BOTTOM, TRUE); // 初始化插补器 interpolators = new Interpolator[4]; interpolators[0] = line; interpolators[1] = acc; interpolators[2] = dce; interpolators[3] = accdec; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight(); } public void addHeart() { ImageView imageView = new ImageView(getContext()); // 随机选一个 imageView.setImageDrawable(drawables[random.nextInt(3)]); imageView.setLayoutParams(lp); addView(imageView); Animator set = getAnimator(imageView); set.addListener(new AnimEndListener(imageView)); set.start(); } private Animator getAnimator(View target) { AnimatorSet set = getEnterAnimtor(target); ValueAnimator bezierValueAnimator = getBezierValueAnimator(target); AnimatorSet finalSet = new AnimatorSet(); finalSet.playSequentially(set); finalSet.playSequentially(set, bezierValueAnimator); finalSet.setInterpolator(interpolators[random.nextInt(4)]); finalSet.setTarget(target); return finalSet; } private AnimatorSet getEnterAnimtor(final View target) { ObjectAnimator alpha = ObjectAnimator.ofFloat(target, View.ALPHA, 0.2f, 1f); ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, 0.2f, 1f); ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, 0.2f, 1f); AnimatorSet enter = new AnimatorSet(); enter.setDuration(500); enter.setInterpolator(new LinearInterpolator()); enter.playTogether(alpha, scaleX, scaleY); enter.setTarget(target); return enter; } private ValueAnimator getBezierValueAnimator(View target) { // 初始化一个贝塞尔计算器- - 传入 BezierEvaluator evaluator = new BezierEvaluator(getPointF(2), getPointF(1)); // 这里最好画个图 理解一下 传入了起点 和 终点 ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF( (mWidth - dWidth) / 2, mHeight - dHeight), new PointF(random.nextInt(getWidth()), 0)); animator.addUpdateListener(new BezierListenr(target)); animator.setTarget(target); animator.setDuration(3000); return animator; } /** * 获取中间的两个 点 * * @param scale */ private PointF getPointF(int scale) { PointF pointF = new PointF(); pointF.x = random.nextInt((mWidth - 100));// 减去100 是为了控制 x轴活动范围,看效果 随意~~ // 再Y轴上 为了确保第二个点 在第一个点之上,我把Y分成了上下两半 这样动画效果好一些 也可以用其他方法 pointF.y = random.nextInt((mHeight - 100)) / scale; return pointF; } private class BezierListenr implements ValueAnimator.AnimatorUpdateListener { private View target; public BezierListenr(View target) { this.target = target; } @Override public void onAnimationUpdate(ValueAnimator animation) { // 这里获取到贝塞尔曲线计算出来的的x y值 赋值给view 这样就能让爱心随着曲线走啦 PointF pointF = (PointF) animation.getAnimatedValue(); target.setX(pointF.x); target.setY(pointF.y); // 这里顺便做一个alpha动画 target.setAlpha(1 - animation.getAnimatedFraction()); } } private class AnimEndListener extends AnimatorListenerAdapter { private View target; public AnimEndListener(View target) { this.target = target; } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); // 因为不停的add 导致子view数量只增不减,所以在view动画结束后remove掉 removeView((target)); } } } ~~~ ## activity_main.xml ~~~ 布局的实现 ~~~ ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000" > <Button android:id="@+id/btn_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="飞舞吧!" /> <com.lgl.heartfaom.PeriscopeLayout android:id="@+id/periscope" android:layout_width="match_parent" android:layout_height="match_parent" > </com.lgl.heartfaom.PeriscopeLayout> </RelativeLayout> ~~~ ## MainActivity ~~~ 接着就是怎么去使用它了 ~~~ ~~~ package com.lgl.heartfaom; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { private Button btn_start; // 心型气泡 private PeriscopeLayout periscopeLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化 periscopeLayout = (PeriscopeLayout) findViewById(R.id.periscope); btn_start = (Button) findViewById(R.id.btn_start); btn_start.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 调用添加泡泡的方法 periscopeLayout.addHeart(); } }); } } ~~~ > 好,我们接下来就可以运行一下试试实际上的效果了 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1a0a509.jpg) > 觉得不错的点个赞哦! ## Demo下载地址:[http://download.csdn.net/detail/qq_26787115/9422603](http://download.csdn.net/detail/qq_26787115/9422603)
';

(七)——飞机升空特效,一键清理缓存,灵活运用动画会有不一样的感受

最后更新于:2022-04-01 09:36:21

# Android特效专辑(七)——飞机升空特效,一键清理缓存,灵活运用属性动画 > 最近的几篇博文反响还不错,也会继续的写下去的,关于这些特效的专辑,大多数也是借鉴大神的,最近由于工作的关系,会深入的了解一下Android BLE与硬件设备的串口通讯相关的内容,也会时不时的分享出来,当然,大家是一起学习,我毕竟也是初学者,今天讲的是小火箭的动画效果,用到了基础动画做了一些偷梁换柱的事情,明天还是后天,再更新一个心型起泡飞舞的特效,就不会这么持续的更新特效专辑这一系列了,毕竟《Only》这个软件也正在开发当中,最重要的还是交互,不是特效,特效多了反而会鸡肋,并且机型适配方面可能导致一些不确定的因素也是有的,所以,这几天可能会多分享一些蓝牙相关的东西了,当然,会持续的更新Android实用案例这个系列,很多的Android技术等你一起来玩。 我们先来看看今天的效果图吧: ### 截图 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c18d923e.jpg "") > 感觉是不是挺好玩的?其实,我其实就是用了三张图片,用黑色背景我们看看这三张图片 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c190fdec.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c192a830.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c193b98f.jpg "") > 好的,知道了这三张图片,我们在res文件下新建一个anim文件夹,然后把他们三个的动画加上 ### cloud.xml ~~~ <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <alpha android:duration="800" android:fromAlpha="0" android:startOffset="300" android:toAlpha="1.0" > </alpha> <translate android:duration="800" android:fromYDelta="100%" android:interpolator="@android:anim/accelerate_interpolator" android:startOffset="300" android:toYDelta="0%" /> <alpha android:duration="500" android:fromAlpha="1.0" android:startOffset="2050" android:toAlpha="0" > </alpha> <translate android:duration="650" android:fromYDelta="0%" android:startOffset="2050" android:toYDelta="100%" /> </set> ~~~ ### launcher.xml ~~~ <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="200" android:fromYDelta="100%" android:startOffset="2050" android:toYDelta="0%" /> <alpha android:duration="450" android:fromAlpha="1.0" android:startOffset="2250" android:toAlpha="0" > </alpha> </set> ~~~ ### rocket.xml ~~~ <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="500" android:fromYDelta="100%" android:toYDelta="-5%p" /> <translate android:duration="500" android:fromYDelta="0%p" android:startOffset="500" android:toYDelta="4%p" /> <translate android:duration="500" android:fromYDelta="0%p" android:startOffset="1000" android:toYDelta="-1.5%p" /> <translate android:duration="300" android:fromYDelta="0%p" android:startOffset="1750" android:toYDelta="8%p" /> <translate android:duration="400" android:fromYDelta="0%p" android:interpolator="@android:anim/accelerate_interpolator" android:startOffset="2050" android:toYDelta="-108%p" /> </set> ~~~ ### activity_main.xml ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" > <ImageView android:id="@+id/cloud" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="@drawable/cloud" android:visibility="invisible" /> <ImageView android:id="@+id/launcher" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:src="@drawable/launcher" android:visibility="invisible" /> <ImageView android:id="@+id/rocket" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:paddingLeft="8dp" android:src="@drawable/rocket" android:visibility="invisible" /> <Button android:id="@+id/getup" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:text="启动火箭" android:textColor="#ffffff" android:textSize="20dp" /> </RelativeLayout> ~~~ ~~~ 三个动画,然后就是布局,一个按钮启动,然后三张图片显示出来并且出来一个动画,是不是思路越来越清晰了? ~~~ ### MainActivity ~~~ package com.lgl.recht; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.AnimationUtils; import android.widget.Button; public class MainActivity extends Activity implements OnClickListener { private Handler mHandler = new Handler(); private Button btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (Button) findViewById(R.id.getup); btn.setOnClickListener(this); } private void launcherTheRocket() { mHandler.postDelayed(new Runnable() { @Override public void run() { final View rocket = findViewById(R.id.rocket); //初始化 Animation rocketAnimation = AnimationUtils.loadAnimation( getApplicationContext(), R.anim.rocket); //设置动画监听 rocketAnimation .setAnimationListener(new VisibilityAnimationListener( rocket)); //开启 rocket.startAnimation(rocketAnimation); final View cloud = findViewById(R.id.cloud); Animation cloudAnimation = AnimationUtils.loadAnimation( getApplicationContext(), R.anim.cloud); cloudAnimation .setAnimationListener(new VisibilityAnimationListener( cloud)); cloud.startAnimation(cloudAnimation); final View launcher = findViewById(R.id.launcher); Animation launcherAnimation = AnimationUtils.loadAnimation( getApplicationContext(), R.anim.launcher); launcherAnimation .setAnimationListener(new VisibilityAnimationListener( launcher)); launcher.startAnimation(launcherAnimation); } }, 150); } public class VisibilityAnimationListener implements AnimationListener { private View mVisibilityView; public VisibilityAnimationListener(View view) { mVisibilityView = view; } public void setVisibilityView(View view) { mVisibilityView = view; } //动画开始 @Override public void onAnimationStart(Animation animation) { Log.i("START", "..."); if (mVisibilityView != null) { mVisibilityView.setVisibility(View.VISIBLE); // mVisibilityView.setVisibility(View.GONE); } } //动画结束 @Override public void onAnimationEnd(Animation animation) { Log.i("END", "..."); if (mVisibilityView != null) { mVisibilityView.setVisibility(View.GONE); } } @Override public void onAnimationRepeat(Animation animation) { } } @Override public void onClick(View v) { //启动 launcherTheRocket(); } } ~~~ ~~~ 好的,我们可以来运行一下了 ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1954c6c.jpg "") ~~~ 这里我们并没有添加清理缓存的功能,我们在下面实现,这里提供一个工具类 ~~~ ### DataCleanManager ~~~ package com.lgl.data; import java.io.File; import java.math.BigDecimal; import android.content.Context; import android.os.Environment; /** * 本应用数据清除管理器 */ public class DataCleanManager { public static String getTotalCacheSize(Context context) throws Exception { long cacheSize = getFolderSize(context.getCacheDir()); if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { cacheSize += getFolderSize(context.getExternalCacheDir()); } return getFormatSize(cacheSize); } public static void clearAllCache(Context context) { deleteDir(context.getCacheDir()); if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { deleteDir(context.getExternalCacheDir()); } } private static boolean deleteDir(File dir) { if (dir != null && dir.isDirectory()) { String[] children = dir.list(); for (int i = 0; i < children.length; i++) { boolean success = deleteDir(new File(dir, children[i])); if (!success) { return false; } } } return dir.delete(); } // 获取文件 // Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/ // 目录,一般放一些长时间保存的数据 // Context.getExternalCacheDir() --> // SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据 public static long getFolderSize(File file) throws Exception { long size = 0; try { File[] fileList = file.listFiles(); for (int i = 0; i < fileList.length; i++) { // 如果下面还有文件 if (fileList[i].isDirectory()) { size = size + getFolderSize(fileList[i]); } else { size = size + fileList[i].length(); } } } catch (Exception e) { e.printStackTrace(); } return size; } /** * 格式化单位 * * @param size */ public static String getFormatSize(double size) { double kiloByte = size / 1024; if (kiloByte < 1) { return size + "Byte"; } double megaByte = kiloByte / 1024; if (megaByte < 1) { BigDecimal result1 = new BigDecimal(Double.toString(kiloByte)); return result1.setScale(2, BigDecimal.ROUND_HALF_UP) .toPlainString() + "KB"; } double gigaByte = megaByte / 1024; if (gigaByte < 1) { BigDecimal result2 = new BigDecimal(Double.toString(megaByte)); return result2.setScale(2, BigDecimal.ROUND_HALF_UP) .toPlainString() + "MB"; } double teraBytes = gigaByte / 1024; if (teraBytes < 1) { BigDecimal result3 = new BigDecimal(Double.toString(gigaByte)); return result3.setScale(2, BigDecimal.ROUND_HALF_UP) .toPlainString() + "GB"; } BigDecimal result4 = new BigDecimal(teraBytes); return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB"; } } ~~~ ### 使用方法 ~~~ private DataCleanManager dm; try { //缓存大小 tv.setText(dm.getTotalCacheSize(this)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //清理缓存 dm.clearAllCache(MainActivity.this); ~~~ ### 大伙觉得好的话,欢迎点个赞,嘿嘿! ### Demo下载地址:[http://download.csdn.net/detail/qq_26787115/9412013](http://download.csdn.net/detail/qq_26787115/9412013)
';

(六)——仿QQ聊天撒花特效,无形装逼,最为致命

最后更新于:2022-04-01 09:36:19

# Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命 > 我的关于特效的专辑已经在CSDN上申请了一个专栏——[http://blog.csdn.net/column/details/liuguilin.html](http://blog.csdn.net/column/details/liuguilin.html) 日后我所写的特效专辑也会以一添加在这个专栏上,今天写的这个特效,是关于聊天的,你肯定遇到过,就是你跟人家聊天的时候,比如发送应(么么哒),然后屏幕上全部就是表情了,今天我们就是做这个,撒花的特效,国际惯例,上图 ### 截图 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c186d543.jpg "") ~~~ 实现这样的效果,你要知道贝塞尔曲线,何谓贝塞尔曲线?其实就是曲线,嘿嘿,关于曲线的概念大家可以去 ~~~ [Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解](http://blog.csdn.net/qq_26787115/article/details/50466655) 中看下,我们这里就直接写了 ### 1.activity_main.xml ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > //撒花的区域 <RelativeLayout android:id="@+id/rlt_animation_layout" android:layout_width="match_parent" android:layout_height="match_parent" > </RelativeLayout> <Button android:id="@+id/btn_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="23dp" android:text="开始撒花" /> </RelativeLayout> ~~~ ### 2.Fllower ~~~ 传参类 ~~~ ~~~ package com.lgl.test; import android.graphics.Bitmap; import android.graphics.Path; import java.io.Serializable; public class Fllower implements Serializable { private static final long serialVersionUID = 1L; private Bitmap image; private float x; private float y; private Path path; private float value; public Bitmap getResId() { return image; } public void setResId(Bitmap img) { this.image = img; } public float getX() { return x; } public void setX(float x) { this.x = x; } public float getY() { return y; } public void setY(float y) { this.y = y; } public Path getPath() { return path; } public void setPath(Path path) { this.path = path; } public float getValue() { return value; } public void setValue(float value) { this.value = value; } @Override public String toString() { return "Fllower [ x=" + x + ", y=" + y + ", path=" + path + ", value=" + value + "]"; } } ~~~ ### 3.FllowerAnimation ~~~ 动画类 ~~~ ~~~ package com.lgl.test; import java.util.ArrayList; import java.util.List; import java.util.Random; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathMeasure; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; /** * 撒花 用到的知识点: 1、android属性动画 2、Path路径绘制 3、贝塞尔曲线 */ public class FllowerAnimation extends View implements AnimatorUpdateListener { /** * 动画改变的属性值 */ private float phase1 = 0f; private float phase2 = 0f; private float phase3 = 0f; /** * 小球集合 */ private List<Fllower> fllowers1 = new ArrayList<Fllower>(); private List<Fllower> fllowers2 = new ArrayList<Fllower>(); private List<Fllower> fllowers3 = new ArrayList<Fllower>(); /** * 动画播放的时间 */ private int time = 4000; /** * 动画间隔 */ private int delay = 400; int[] ylocations = { -100, -50, -25, 0 }; /** * 资源ID */ // private int resId = R.drawable.fllower_love; public FllowerAnimation(Context context) { super(context); init(context); // this.resId = resId; } @SuppressWarnings("deprecation") private void init(Context context) { WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); width = wm.getDefaultDisplay().getWidth(); height = (int) (wm.getDefaultDisplay().getHeight() * 3 / 2f); mPaint = new Paint(); mPaint.setAntiAlias(true); // mPaint.setStrokeWidth(2); // mPaint.setColor(Color.BLUE); // mPaint.setStyle(Style.STROKE); pathMeasure = new PathMeasure(); builderFollower(fllowerCount, fllowers1); builderFollower(fllowerCount, fllowers2); builderFollower(fllowerCount, fllowers3); } /** * 宽度 */ private int width = 0; /** * 高度 */ private int height = 0; /** * 曲线高度个数分割 */ private int quadCount = 10; /** * 曲度 */ private float intensity = 0.2f; /** * 第一批个数 */ private int fllowerCount = 4; /** * 创建花 */ private void builderFollower(int count, List<Fllower> fllowers) { int max = (int) (width * 3 / 4f); int min = (int) (width / 4f); Random random = new Random(); for (int i = 0; i < count; i++) { int s = random.nextInt(max) % (max - min + 1) + min; Path path = new Path(); CPoint CPoint = new CPoint(s, ylocations[random.nextInt(3)]); List<CPoint> points = builderPath(CPoint); drawFllowerPath(path, points); Fllower fllower = new Fllower(); fllower.setPath(path); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lift_flower); fllower.setResId(bitmap); fllowers.add(fllower); } } /** * 画曲线 * * @param path * @param points */ private void drawFllowerPath(Path path, List<CPoint> points) { if (points.size() > 1) { for (int j = 0; j < points.size(); j++) { CPoint point = points.get(j); if (j == 0) { CPoint next = points.get(j + 1); point.dx = ((next.x - point.x) * intensity); point.dy = ((next.y - point.y) * intensity); } else if (j == points.size() - 1) { CPoint prev = points.get(j - 1); point.dx = ((point.x - prev.x) * intensity); point.dy = ((point.y - prev.y) * intensity); } else { CPoint next = points.get(j + 1); CPoint prev = points.get(j - 1); point.dx = ((next.x - prev.x) * intensity); point.dy = ((next.y - prev.y) * intensity); } // create the cubic-spline path if (j == 0) { path.moveTo(point.x, point.y); } else { CPoint prev = points.get(j - 1); path.cubicTo(prev.x + prev.dx, (prev.y + prev.dy), point.x - point.dx, (point.y - point.dy), point.x, point.y); } } } } /** * 曲线摇摆的幅度 */ private int range = (int) TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources() .getDisplayMetrics()); /** * 画路径 * * @param point * @return */ private List<CPoint> builderPath(CPoint point) { List<CPoint> points = new ArrayList<CPoint>(); Random random = new Random(); for (int i = 0; i < quadCount; i++) { if (i == 0) { points.add(point); } else { CPoint tmp = new CPoint(0, 0); if (random.nextInt(100) % 2 == 0) { tmp.x = point.x + random.nextInt(range); } else { tmp.x = point.x - random.nextInt(range); } tmp.y = (int) (height / (float) quadCount * i); points.add(tmp); } } return points; } /** * 画笔 */ private Paint mPaint; /** * 测量路径的坐标位置 */ private PathMeasure pathMeasure = null; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawFllower(canvas, fllowers1); drawFllower(canvas, fllowers2); drawFllower(canvas, fllowers3); } /** * 高度往上偏移量,把开始点移出屏幕顶部 */ private float dy = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics()); /** * @param canvas * @param fllowers */ private void drawFllower(Canvas canvas, List<Fllower> fllowers) { for (Fllower fllower : fllowers) { float[] pos = new float[2]; // canvas.drawPath(fllower.getPath(),mPaint); pathMeasure.setPath(fllower.getPath(), false); pathMeasure.getPosTan(height * fllower.getValue(), pos, null); // canvas.drawCircle(pos[0], pos[1], 10, mPaint); canvas.drawBitmap(fllower.getResId(), pos[0], pos[1] - dy, null); } } ObjectAnimator mAnimator1; ObjectAnimator mAnimator2; ObjectAnimator mAnimator3; public void startAnimation() { if (mAnimator1 != null && mAnimator1.isRunning()) { mAnimator1.cancel(); } mAnimator1 = ObjectAnimator.ofFloat(this, "phase1", 0f, 1f); mAnimator1.setDuration(time); mAnimator1.addUpdateListener(this); mAnimator1.start(); mAnimator1.setInterpolator(new AccelerateInterpolator(1f)); if (mAnimator2 != null && mAnimator2.isRunning()) { mAnimator2.cancel(); } mAnimator2 = ObjectAnimator.ofFloat(this, "phase2", 0f, 1f); mAnimator2.setDuration(time); mAnimator2.addUpdateListener(this); mAnimator2.start(); mAnimator2.setInterpolator(new AccelerateInterpolator(1f)); mAnimator2.setStartDelay(delay); if (mAnimator3 != null && mAnimator3.isRunning()) { mAnimator3.cancel(); } mAnimator3 = ObjectAnimator.ofFloat(this, "phase3", 0f, 1f); mAnimator3.setDuration(time); mAnimator3.addUpdateListener(this); mAnimator3.start(); mAnimator3.setInterpolator(new AccelerateInterpolator(1f)); mAnimator3.setStartDelay(delay * 2); } /** * 跟新小球的位置 * * @param value * @param fllowers */ private void updateValue(float value, List<Fllower> fllowers) { for (Fllower fllower : fllowers) { fllower.setValue(value); } } /** * 动画改变回调 */ @Override public void onAnimationUpdate(ValueAnimator arg0) { updateValue(getPhase1(), fllowers1); updateValue(getPhase2(), fllowers2); updateValue(getPhase3(), fllowers3); Log.i(tag, getPhase1() + ""); invalidate(); } public float getPhase1() { return phase1; } public void setPhase1(float phase1) { this.phase1 = phase1; } public float getPhase2() { return phase2; } public void setPhase2(float phase2) { this.phase2 = phase2; } public float getPhase3() { return phase3; } public void setPhase3(float phase3) { this.phase3 = phase3; } private String tag = this.getClass().getSimpleName(); private class CPoint { public float x = 0f; public float y = 0f; /** * x-axis distance */ public float dx = 0f; /** * y-axis distance */ public float dy = 0f; public CPoint(float x, float y) { this.x = x; this.y = y; } } } ~~~ # 4.MainActivity ~~~ 接着就看我们使用 ~~~ ~~~ package com.lgl.test; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.RelativeLayout; public class MainActivity extends Activity { private Button btn_start; // 撒花特效 private RelativeLayout rlt_animation_layout; private FllowerAnimation fllowerAnimation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 撒花初始化 rlt_animation_layout = (RelativeLayout) findViewById(R.id.rlt_animation_layout); rlt_animation_layout.setVisibility(View.VISIBLE); fllowerAnimation = new FllowerAnimation(this); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); fllowerAnimation.setLayoutParams(params); rlt_animation_layout.addView(fllowerAnimation); btn_start = (Button) findViewById(R.id.btn_start); btn_start.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 开始撒花 fllowerAnimation.startAnimation(); } }); } } ~~~ ~~~ 好,我们现在来看看效果 ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c18ad790.jpg "") ~~~ 好的,你也赶快去试一下吧! ~~~ ### Demo下载:[http://download.csdn.net/detail/qq_26787115/9410578](http://download.csdn.net/detail/qq_26787115/9410578)
';

(五)——自定义圆形头像和仿MIUI卸载动画—粒子爆炸

最后更新于:2022-04-01 09:36:17

# Android特效专辑(五)——自定义圆形头像和仿MIUI卸载动画—粒子爆炸 > 好的,各位亲爱的朋友,今天讲的特效还是比较炫的,首先,我们会讲一个自定义圆形的imageView,接着,我们会来实现粒子爆炸的特效,按照国际惯例,无图无真相的没这个效果也是模仿大神的,现在应用在了我的《Only》上 ### 截图 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c174d678.jpg "") ~~~ 好的,我们新建一个工程——AnimView,我们要用到的图片 ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1792fd5.jpg "") ### 一.自定义圆形头像—— ~~~ 直接开写了,要实现的东西都在注释上了 ~~~ ### 1.编写自定义属性attr.xml ~~~ <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="roundedimageview"> <attr name="border_thickness" format="dimension" /> <attr name="border_inside_color" format="color" /> <attr name="border_outside_color" format="color"></attr> </declare-styleable> </resources> ~~~ ### 2.自定义View ~~~ 紧接着我们就可以编写这个类了 ~~~ ~~~ package com.lgl.animview; /** * 圆形头像 * Created by LGL on 2016/1/12. */ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.NinePatchDrawable; import android.util.AttributeSet; import android.widget.ImageView; /** * 圆形ImageView,可设置最多两个宽度不同且颜色不同的圆形边框。 设置颜色在xml布局文件中由自定义属性配置参数指定 */ public class RoundImageView extends ImageView { private int mBorderThickness = 0; private Context mContext; private int defaultColor = 0xFFFFFFFF; // 如果只有其中一个有值,则只画一个圆形边框 private int mBorderOutsideColor = 0; private int mBorderInsideColor = 0; // 控件默认长、宽 private int defaultWidth = 0; private int defaultHeight = 0; public RoundImageView(Context context) { super(context); mContext = context; } public RoundImageView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; setCustomAttributes(attrs); } public RoundImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; setCustomAttributes(attrs); } private void setCustomAttributes(AttributeSet attrs) { // 获取自定义的属性 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.roundedimageview); mBorderThickness = a.getDimensionPixelSize( R.styleable.roundedimageview_border_thickness, 0); mBorderOutsideColor = a .getColor(R.styleable.roundedimageview_border_outside_color, defaultColor); mBorderInsideColor = a.getColor( R.styleable.roundedimageview_border_inside_color, defaultColor); } @Override protected void onDraw(Canvas canvas) { Drawable drawable = getDrawable(); if (drawable == null) { return; } if (getWidth() == 0 || getHeight() == 0) { return; } this.measure(0, 0); if (drawable.getClass() == NinePatchDrawable.class) return; Bitmap b = ((BitmapDrawable) drawable).getBitmap(); Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true); if (defaultWidth == 0) { defaultWidth = getWidth(); } if (defaultHeight == 0) { defaultHeight = getHeight(); } int radius = 0; if (mBorderInsideColor != defaultColor && mBorderOutsideColor != defaultColor) {// 定义画两个边框,分别为外圆边框和内圆边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - 2 * mBorderThickness; // 画内圆 drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderInsideColor); // 画外圆 drawCircleBorder(canvas, radius + mBorderThickness + mBorderThickness / 2, mBorderOutsideColor); } else if (mBorderInsideColor != defaultColor && mBorderOutsideColor == defaultColor) {// 定义画一个边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - mBorderThickness; drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderInsideColor); } else if (mBorderInsideColor == defaultColor && mBorderOutsideColor != defaultColor) {// 定义画一个边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - mBorderThickness; drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderOutsideColor); } else {// 没有边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2; } Bitmap roundBitmap = getCroppedRoundBitmap(bitmap, radius); canvas.drawBitmap(roundBitmap, defaultWidth / 2 - radius, defaultHeight / 2 - radius, null); } /** * 获取裁剪后的圆形图片 */ public Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) { Bitmap scaledSrcBmp; int diameter = radius * 2; // 为了防止宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片 int bmpWidth = bmp.getWidth(); int bmpHeight = bmp.getHeight(); int squareWidth = 0, squareHeight = 0; int x = 0, y = 0; Bitmap squareBitmap; if (bmpHeight > bmpWidth) {// 高大于宽 squareWidth = squareHeight = bmpWidth; x = 0; y = (bmpHeight - bmpWidth) / 2; // 截取正方形图片 squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else if (bmpHeight < bmpWidth) {// 宽大于高 squareWidth = squareHeight = bmpHeight; x = (bmpWidth - bmpHeight) / 2; y = 0; squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else { squareBitmap = bmp; } if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) { scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true); } else { scaledSrcBmp = squareBitmap; } Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); Paint paint = new Paint(); Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight()); paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); canvas.drawARGB(0, 0, 0, 0); canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(scaledSrcBmp, rect, rect, paint); bmp = null; squareBitmap = null; scaledSrcBmp = null; return output; } /** * 边缘画圆 */ private void drawCircleBorder(Canvas canvas, int radius, int color) { Paint paint = new Paint(); /* 去锯齿 */ paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); paint.setColor(color); /* 设置paint的 style 为STROKE:空心 */ paint.setStyle(Paint.Style.STROKE); /* 设置paint的外框宽度 */ paint.setStrokeWidth(mBorderThickness); canvas.drawCircle(defaultWidth / 2, defaultHeight / 2, radius, paint); } } ~~~ ### 3.引用 ~~~ 引用起来就比较简单了,我们首先来引入他的命名空间 ~~~ ~~~ xmlns:imagecontrol="http://schemas.android.com/apk/res-auto" ~~~ ~~~ 然后我们直接写xml ~~~ ~~~ <com.lgl.animview.RoundImageView android:id="@+id/iv_round" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/photo" imagecontrol:border_inside_color="#bc0978" imagecontrol:border_outside_color="#ba3456" imagecontrol:border_thickness="1dp" /> ~~~ ~~~ 好的,让我们运行下吧 ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c17af7f9.jpg "") ### 二.MUI卸载动画——粒子爆炸 ~~~ 关于这个粒子特效,在开篇的时候已经展示了效果,那么我们接下来,要怎么做尼? ~~~ ### 1.ParticleUtils ~~~ 用于粒子动画的单位转换 ~~~ ~~~ package com.lgl.animview; import android.content.res.Resources; /** * 粒子动画 */ public class ParticleUtils { /** * 密度 */ public static final float DENSITY = Resources.getSystem().getDisplayMetrics().density; public static int dp2px(int dp) { return Math.round(dp * DENSITY); } } ~~~ ### 2.Particle ~~~ 用于爆破效果的分子计算 ~~~ ~~~ package com.lgl.animview; import java.util.Random; import android.graphics.Point; import android.graphics.Rect; /** * Created by lgl on 16/01/14. 爆破粒子 */ public class Particle { public static final int PART_WH = 8; // 默认小球宽高 // 原本的值(不可变) // float originCX; // float originCY; // float originRadius; // 实际的值(可变) float cx; // center x of circle float cy; // center y of circle float radius; int color; float alpha; static Random random = new Random(); Rect mBound; public static Particle generateParticle(int color, Rect bound, Point point) { int row = point.y; // 行是高 int column = point.x; // 列是宽 Particle particle = new Particle(); particle.mBound = bound; particle.color = color; particle.alpha = 1f; particle.radius = PART_WH; particle.cx = bound.left + PART_WH * column; particle.cy = bound.top + PART_WH * row; return particle; } public void advance(float factor) { cx = cx + factor * random.nextInt(mBound.width()) * (random.nextFloat() - 0.5f); cy = cy + factor * random.nextInt(mBound.height() / 2); radius = radius - factor * random.nextInt(2); alpha = (1f - factor) * (1 + random.nextFloat()); } } ~~~ ### 3.ExplosionAnimator ~~~ 属性动画,用于动画展示 ~~~ ~~~ package com.lgl.animview; import android.animation.ValueAnimator; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.view.View; /** * Created by lgl on 16/01/14. */ public class ExplosionAnimator extends ValueAnimator { public static final int DEFAULT_DURATION = 1500; private Particle[][] mParticles; private Paint mPaint; private View mContainer; public ExplosionAnimator(View view, Bitmap bitmap, Rect bound) { mPaint = new Paint(); mContainer = view; setFloatValues(0.0f, 1.0f); setDuration(DEFAULT_DURATION); mParticles = generateParticles(bitmap, bound); } private Particle[][] generateParticles(Bitmap bitmap, Rect bound) { int w = bound.width(); int h = bound.height(); int partW_Count = w / Particle.PART_WH; // 横向个数 int partH_Count = h / Particle.PART_WH; // 竖向个数 int bitmap_part_w = bitmap.getWidth() / partW_Count; int bitmap_part_h = bitmap.getHeight() / partH_Count; Particle[][] particles = new Particle[partH_Count][partW_Count]; Point point = null; for (int row = 0; row < partH_Count; row++) { // 行 for (int column = 0; column < partW_Count; column++) { // 列 // 取得当前粒子所在位置的颜色 int color = bitmap.getPixel(column * bitmap_part_w, row * bitmap_part_h); point = new Point(column, row); // x是列,y是行 particles[row][column] = Particle.generateParticle(color, bound, point); } } return particles; } public void draw(Canvas canvas) { if (!isStarted()) { // 动画结束时停止 return; } for (Particle[] particle : mParticles) { for (Particle p : particle) { p.advance((Float) getAnimatedValue()); mPaint.setColor(p.color); // mPaint.setAlpha((int) (255 * p.alpha)); //只是这样设置,透明色会显示为黑色 mPaint.setAlpha((int) (Color.alpha(p.color) * p.alpha)); // 这样透明颜色就不是黑色了 canvas.drawCircle(p.cx, p.cy, p.radius, mPaint); } } mContainer.invalidate(); } @Override public void start() { super.start(); mContainer.invalidate(); } } ~~~ ### 4.ExplosionField ~~~ 开始执行这个实例的动画了 ~~~ ~~~ package com.lgl.animview; import java.util.ArrayList; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.Window; /** * Created by lgl on 16/01/14. */ public class ExplosionField extends View { private static final String TAG = "ExplosionField"; private static final Canvas mCanvas = new Canvas(); private ArrayList<ExplosionAnimator> explosionAnimators; private OnClickListener onClickListener; public ExplosionField(Context context) { super(context); init(); } public ExplosionField(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { explosionAnimators = new ArrayList<ExplosionAnimator>(); attach2Activity((Activity) getContext()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (ExplosionAnimator animator : explosionAnimators) { animator.draw(canvas); } } /** * 爆破 * * @param view * 使得该view爆破 */ public void explode(final View view) { Rect rect = new Rect(); view.getGlobalVisibleRect(rect); // 得到view相对于整个屏幕的坐标 rect.offset(0, -ParticleUtils.dp2px(25)); // 去掉状态栏高度 final ExplosionAnimator animator = new ExplosionAnimator(this, createBitmapFromView(view), rect); explosionAnimators.add(animator); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { view.animate().alpha(0f).setDuration(150).start(); } @Override public void onAnimationEnd(Animator animation) { view.animate().alpha(1f).setDuration(150).start(); // 动画结束时从动画集中移除 explosionAnimators.remove(animation); animation = null; } }); animator.start(); } private Bitmap createBitmapFromView(View view) { /* * 为什么屏蔽以下代码段? 如果ImageView直接得到位图,那么当它设置背景(backgroud)时,不会读取到背景颜色 */ // if (view instanceof ImageView) { // Drawable drawable = ((ImageView)view).getDrawable(); // if (drawable != null && drawable instanceof BitmapDrawable) { // return ((BitmapDrawable) drawable).getBitmap(); // } // } // view.clearFocus(); //不同焦点状态显示的可能不同——(azz:不同就不同有什么关系?) Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); if (bitmap != null) { synchronized (mCanvas) { mCanvas.setBitmap(bitmap); view.draw(mCanvas); mCanvas.setBitmap(null); // 清除引用 } } return bitmap; } /** * 给Activity加上全屏覆盖的ExplosionField */ private void attach2Activity(Activity activity) { ViewGroup rootView = (ViewGroup) activity .findViewById(Window.ID_ANDROID_CONTENT); ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); rootView.addView(this, lp); } /** * 希望谁有破碎效果,就给谁加Listener * * @param view * 可以是ViewGroup */ public void addListener(View view) { if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; int count = viewGroup.getChildCount(); for (int i = 0; i < count; i++) { addListener(viewGroup.getChildAt(i)); } } else { view.setClickable(true); view.setOnClickListener(getOnClickListener()); } } private OnClickListener getOnClickListener() { if (null == onClickListener) { onClickListener = new OnClickListener() { @Override public void onClick(View v) { ExplosionField.this.explode(v); // view.setOnClickListener(null); // 用过一次就不需要了 } }; } return onClickListener; } } ~~~ ### 5.MainActivity ~~~ 好的,一切准备好了之后我们就可以使用了 ~~~ ~~~ package com.lgl.animview; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity { // 实例化粒子动画 private ExplosionField explosionField; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); explosionField = new ExplosionField(this); // 绑定哪个控件哪个控件就有效果,如果需要整个layout,只要绑定根布局的id即可 explosionField.addListener(findViewById(R.id.iv_round)); } } ~~~ ~~~ 在xml中我们什么也不用做,好的,让我们来运行一下 ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c17de8ca.jpg "") ~~~ 好的,本篇博客也到此结束了,喜欢的点个赞 ~~~ ### Demo下载:[http://download.csdn.net/detail/qq_26787115/9409139](http://download.csdn.net/detail/qq_26787115/9409139)
';

(四)——APP主页框架TabHost绑定ViewPager的替换者TabLayout

最后更新于:2022-04-01 09:36:14

# Android特效专辑(四)——APP主页框架TabHost绑定ViewPager的替换者TabLayout > 现在很多app都在追求简单明了,功能又要强大,不过我还是喜欢之前的app风格,就是TabHost,在现在也是有许多app在用,不过选项卡可能都放在下面了,我们想要tabhost有viewpager的滑动效果,其实也很简单,那就是fragment,在这之前,我都是很复杂的把它们全部绑定在一起联动的,但是现在就要简单很多了 ,我们用Android Studio开发 先看下效果图吧 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c17315a0.jpg "") ~~~ 我们这次使用到的是TabLayout ~~~ ### activity_main.xml ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.TabLayout android:id="@+id/id_tab_layout" android:background="#ffffff" app:tabGravity="fill" app:tabMode="fixed" app:tabIndicatorColor="#0ddcff" app:tabTextColor="#000000" app:tabSelectedTextColor="#0ddcff" android:layout_width="match_parent" android:layout_height="wrap_content"> </android.support.design.widget.TabLayout> <android.support.v4.view.ViewPager android:id="@+id/id_view_pager" android:layout_width="match_parent" android:layout_height="wrap_content"> </android.support.v4.view.ViewPager> </LinearLayout> ~~~ ### MainActivity ~~~ package com.app.gaohailong.festivalmessage; import android.os.Bundle; import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private TabLayout mTablayout; private ViewPager mViewPager; private String[] mTitles = new String[]{"节日短信", "发送记录", "个人设置"}; private List<Fragment> mFragments; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); } private void initViews() { //初始化View mTablayout = (TabLayout) findViewById(R.id.id_tab_layout); mViewPager = (ViewPager) findViewById(R.id.id_view_pager); //初始化List<Fragment> mFragments = new ArrayList<>(); Fragment mFestivalFragment = new FestivalFragment(); Fragment mRecordFragment = new RecordFragment(); Fragment myFragment = new MyFragment(); mFragments.add(mFestivalFragment); mFragments.add(mRecordFragment); mFragments.add(myFragment); //给ViewPage设置Adapter mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public Fragment getItem(int position) { return mFragments.get(position); } @Override public int getCount() { return mFragments.size(); } @Override public CharSequence getPageTitle(int position) { return mTitles[position]; } }); mTablayout.setupWithViewPager(mViewPager); } } ~~~ ~~~ 记得添加源 ~~~ ~~~ compile 'com.android.support:design:23.1.1' ~~~ ~~~ 这样就可以了,是不是非常的简单?你也可以去试试呢 ~~~
';

(三)——自定义不一样的Toast

最后更新于:2022-04-01 09:36:12

# Android特效专辑(三)——自定义不一样的Toast ~~~ 大家都知道,Android的控件有时候很难满足我们的需求,所以我们需要自定义View。自定义的方式很多,有继承原生控件也有直接自定义View的,今天写的是自定义的Toast,当然,这个不是复写Toast,是换一种表达形式,哈哈,后续会增加自定义View的方方面面 ~~~ > 这里啰嗦几句,我看到网上很多教程写的很不详细,就像是直接CV过来的一样,所以有时间我就会把我需要的东西写起来,然后尽可能的描述的清楚一点,以后我工作用到了就可以直接拿来用了,同时也可以把有用的星系分享给大家,这就是我写博客 的初衷了,经常去墙外面找的好例子,我也是摸透了才放在我博客上,如果以后有疑问或者不懂的,可以加我QQ沟通 ~~~ 先来看看效果图吧 ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1718703.jpg "") ~~~ 是不是挺好看的,如果你看过Toast的源码就会知道,他主要是用了WindowManager,我们也直接来写一个 ~~~ ### TabToast ~~~ package com.lgl.toast; import android.content.Context; import android.text.TextUtils; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; public class TabToast { /** * Toast字体大小 */ private static final float DEFAULT_TEXT_SIZE = 14; /** * Toast字体颜色 */ private static final int DEFAULT_TEXT_COLOR = 0xffffffff; /** * Toast背景颜色 */ private static final int DEFAULT_BG_COLOR = 0xE6f5695a; /** * Toast的高度(单位dp) */ private static final float DEFAULT_TOAST_HEIGHT = 50.0f; private static Context mContext; private volatile static TabToast mInstance; private static Toast mToast; private View layout; private TextView tv; public TabToast(Context context) { mContext = context; } /** * 单例模式 * * @param context * 传入的上下文 * @return TabToast实例 */ private static TabToast getInstance(Context context) { if (mInstance == null) { synchronized (TabToast.class) { if (mInstance == null) { mInstance = new TabToast(context.getApplicationContext()); } } } return mInstance; } private static void getToast(int duration) { //设置位置int gravity, int xOffset, int yOffset if (mToast == null) { mToast = new Toast(mContext); mToast.setGravity(Gravity.BOTTOM, 0, 0); //设置显示时间 mToast.setDuration(duration == Toast.LENGTH_LONG ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT); } } public static void makeText(Context context, String text) { makeText(context, text, Toast.LENGTH_SHORT); } public static void makeText(Context context, String text, int duration) { getInstance(context); getToast(duration); if (mInstance.layout == null || mInstance.tv == null) { LinearLayout container = new LinearLayout(mContext); LinearLayout.LayoutParams rootParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); container.setLayoutParams(rootParams); container.setBackgroundColor(DEFAULT_BG_COLOR); container.setGravity(Gravity.CENTER); mInstance.tv = new TextView(mContext); LinearLayout.LayoutParams tvParams = new LinearLayout.LayoutParams( getScreenWidth(mContext), dp2px(DEFAULT_TOAST_HEIGHT)); mInstance.tv.setLayoutParams(tvParams); mInstance.tv.setPadding(dp2px(5), dp2px(2), dp2px(5), dp2px(2)); mInstance.tv.setGravity(Gravity.CENTER); mInstance.tv.setTextColor(DEFAULT_TEXT_COLOR); mInstance.tv.setMaxLines(2); mInstance.tv.setEllipsize(TextUtils.TruncateAt.END); mInstance.tv.setBackgroundColor(DEFAULT_BG_COLOR); mInstance.tv.setTextSize(DEFAULT_TEXT_SIZE); container.addView(mInstance.tv); mInstance.layout = container; mToast.setView(mInstance.layout); } mInstance.tv.setText(text); mToast.show(); } /** * dp转px * * @param value * dp * @return px */ public static int dp2px(float value) { float scale = mContext.getResources().getDisplayMetrics().density; return (int) (value * scale + 0.5f); } /** * 获得屏幕宽度 * * @param context * Context * @return px */ public static int getScreenWidth(Context context) { WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); float density = outMetrics.density; return (int) (outMetrics.widthPixels * density); } } ~~~ ~~~ 然后直接调用就可以了 ~~~ ~~~ TabToast.makeText(MainActivity.this, "自定义Toast"); ~~~ ### Demo下载地址:[http://download.csdn.net/detail/qq_26787115/9391760](http://download.csdn.net/detail/qq_26787115/9391760)
';

(二)——ViewPager渲染背景颜色渐变(引导页)

最后更新于:2022-04-01 09:36:10

# Android特效专辑(二)——ViewPager渲染背景颜色渐变(引导页) 首页:[http://blog.csdn.net/qq_26787115/article/details/50439020](http://blog.csdn.net/qq_26787115/article/details/50439020) > 首页里面也提到过,自己有意做一款杂七杂八的APP,就是自己喜欢什么就加上面,现在本文说的是引导页,我找了很久才觉得可以的开源项目,自己做了一下修改 开源地址:[https://github.com/TaurusXi/GuideBackgroundColorAnimation](https://github.com/TaurusXi/GuideBackgroundColorAnimation) 先来看看效果图吧! > 图片用的是官方的,嘿嘿 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c16c027e.jpg "") ~~~ 这个做起来,其实也就是加了一个监听变色的效果,我们来写一下把; ~~~ ### layout_main.xml ~~~ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.lgl.viewpager.ColorAnimationView android:id="@+id/ColorAnimationView" android:layout_width="match_parent" android:layout_height="match_parent" /> <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="30dp" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/btn_go" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="43dp" android:background="@drawable/colorbu" android:text="开始" android:visibility="gone" /> </RelativeLayout> </FrameLayout> ~~~ ### MainActivity ~~~ package com.lgl.viewpager; import android.annotation.SuppressLint; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.view.ViewPager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; public class MainActivity extends FragmentActivity { private static final int[] resource = new int[] { R.drawable.welcome1, R.drawable.welcome4, R.drawable.welcome3, R.drawable.welcome4 }; private static final String TAG = MainActivity.class.getSimpleName(); private Button btn_go; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn_go = (Button) findViewById(R.id.btn_go); MyFragmentStatePager adpter = new MyFragmentStatePager( getSupportFragmentManager()); ColorAnimationView colorAnimationView = (ColorAnimationView) findViewById(R.id.ColorAnimationView); ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager); //设置adapter viewPager.setAdapter(adpter); //监听滑动 colorAnimationView.setmViewPager(viewPager, resource.length); colorAnimationView .setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { Log.e("TAG", "onPageScrolled"); } @Override public void onPageSelected(int position) { //Button显示或隐藏 if (position == 3) { btn_go.setVisibility(View.VISIBLE); } else { btn_go.setVisibility(View.GONE); } Log.e("TAG", "onPageSelected"); } @Override public void onPageScrollStateChanged(int state) { Log.e("TAG", "onPageScrollStateChanged"); } }); } public class MyFragmentStatePager extends FragmentStatePagerAdapter { public MyFragmentStatePager(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return new MyFragment(position); } @Override public int getCount() { return resource.length; } } @SuppressLint("ValidFragment") public class MyFragment extends Fragment { private int position; public MyFragment(int position) { this.position = position; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ImageView imageView = new ImageView(getActivity()); imageView.setImageResource(resource[position]); return imageView; } } } ~~~ ### ColorAnimationView ~~~ package com.lgl.viewpager; import android.animation.Animator; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.View; public class ColorAnimationView extends View implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { private static final int RED = 0xffFF8080; private static final int BLUE = 0xff8080FF; private static final int WHITE = 0xffffffff; private static final int GREEN = 0xff80ff80; private static final int DURATION = 3000; ValueAnimator colorAnim = null; private PageChangeListener mPageChangeListener; ViewPager.OnPageChangeListener onPageChangeListener; public void setOnPageChangeListener( ViewPager.OnPageChangeListener onPageChangeListener) { this.onPageChangeListener = onPageChangeListener; } /** * 这是你唯一需要关心的方法 * * @param mViewPager * 你必须在设置 Viewpager 的 Adapter 这后,才能调用这个方法。 * @param obj * ,这个obj实现了 ColorAnimationView.OnPageChangeListener ,实现回调 * @param count * ,viewpager 数据的数量 * @param colors * int... colors ,你需要设置的颜色变化值~~ 如何你传人 空,那么触发默认设置的颜色动画 * */ public void setmViewPager(ViewPager mViewPager, int count, int... colors) { if (mViewPager.getAdapter() == null) { throw new IllegalStateException( "ViewPager does not have adapter instance."); } mPageChangeListener.setViewPagerChildCount(count); mViewPager.setOnPageChangeListener(mPageChangeListener); if (colors.length == 0) { createDefaultAnimation(); } else { createAnimation(colors); } } public ColorAnimationView(Context context) { this(context, null, 0); } public ColorAnimationView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ColorAnimationView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mPageChangeListener = new PageChangeListener(); } private void seek(long seekTime) { if (colorAnim == null) { createDefaultAnimation(); } colorAnim.setCurrentPlayTime(seekTime); } private void createAnimation(int... colors) { if (colorAnim == null) { colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", colors); colorAnim.setEvaluator(new ArgbEvaluator()); colorAnim.setDuration(DURATION); colorAnim.addUpdateListener(this); } } private void createDefaultAnimation() { colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", WHITE, RED, BLUE, GREEN, WHITE); colorAnim.setEvaluator(new ArgbEvaluator()); colorAnim.setDuration(DURATION); colorAnim.addUpdateListener(this); } @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationUpdate(ValueAnimator animation) { invalidate(); } private class PageChangeListener implements ViewPager.OnPageChangeListener { private int viewPagerChildCount; public void setViewPagerChildCount(int viewPagerChildCount) { this.viewPagerChildCount = viewPagerChildCount; } public int getViewPagerChildCount() { return viewPagerChildCount; } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { int count = getViewPagerChildCount() - 1; if (count != 0) { float length = (position + positionOffset) / count; int progress = (int) (length * DURATION); ColorAnimationView.this.seek(progress); } if (onPageChangeListener != null) { onPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } } @Override public void onPageSelected(int position) { if (onPageChangeListener != null) { onPageChangeListener.onPageSelected(position); } } @Override public void onPageScrollStateChanged(int state) { if (onPageChangeListener != null) { onPageChangeListener.onPageScrollStateChanged(state); } } } } ~~~ ### 扁平化Button ~~~ 大家看到这个button是不是挺好看的,这里用到了一个shape属性 ~~~ ~~~ <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"><shape> <solid android:color="#5cb57c" /> <corners android:radius="8dp" /> </shape></item> <item android:state_pressed="false"><shape> <solid android:color="#80ff80" /> <corners android:radius="8dp" /> </shape></item> </selector> ~~~ ### Demo下载地址:[http://download.csdn.net/detail/qq_26787115/9391782](http://download.csdn.net/detail/qq_26787115/9391782)
';

(一)——水波纹过渡特效(首页)

最后更新于:2022-04-01 09:36:07

# Android特效专辑(一)——水波纹过渡特效(首页) > 也是今天看到的一个特效,感觉挺漂亮的,最近也一直在筹划一个APP,就想把他当做APP的首页,然后加些处理,关于首页APP的特效等我完工了再贴出来吧,现在先把这个特效给分享出来,只是稍微改动了一点点而已。 原地址:[http://blog.csdn.net/jdsjlzx/article/details/44601239](http://blog.csdn.net/jdsjlzx/article/details/44601239) ### 先看效果图: ![水波纹](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c16265f0.jpg "") ~~~ 我们先创建一个UIUtils,转换一些单位 ~~~ ~~~ package com.lgl.test; import android.content.Context; import android.util.DisplayMetrics; import android.view.WindowManager; public class UiUtils { static public int getScreenWidthPixels(Context context) { DisplayMetrics dm = new DisplayMetrics(); ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay() .getMetrics(dm); return dm.widthPixels; } static public int dipToPx(Context context, int dip) { return (int) (dip * getScreenDensity(context) + 0.5f); } static public float getScreenDensity(Context context) { try { DisplayMetrics dm = new DisplayMetrics(); ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay() .getMetrics(dm); return dm.density; } catch (Exception e) { return DisplayMetrics.DENSITY_DEFAULT; } } } ~~~ ~~~ 然后写一个WaterRippleView类继承View ~~~ ~~~ package com.lgl.test; 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 WaterRippleView 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 WaterRippleView(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); } } } ~~~ ~~~ 然后你就可以绑定在布局上就可以使用了 ~~~ ~~~ <com.lgl.test.WaterRippleView android:layout_width="fill_parent" android:layout_height="fill_parent" /> ~~~ ~~~ 是不是感觉特效还可以 ~~~ ### Demo下载地址:[http://download.csdn.net/detail/qq_26787115/9384803](http://download.csdn.net/detail/qq_26787115/9384803)
';

前言

最后更新于:2022-04-01 09:36:05

> 原文出处:[Android特效专辑](http://blog.csdn.net/column/details/liuguilin.html) 作者:[qq_26787115](http://blog.csdn.net/qq_26787115) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # Android特效专辑 > 关于Android的一些十分炫酷的特效进行优化,便于使用的博客
';