Android自定義ViewGroup實現(xiàn)絢麗的仿支付寶咻一咻雷達脈沖效果
去年春節(jié)的時候支付寶推行的集福娃活動著實火的不能再火了,更給力的是春晚又可以全民參與咻一咻集福娃活動,集齊五福就可平分億元大紅包,只可惜沒有敬業(yè)福……那時候在家沒事寫了個咻一咻插件,只要到了咻一咻的時間點插件就可以自動的點擊咻一咻來咻紅包,當時只是純粹練習這部分技術代碼沒有公開,后續(xù)計劃寫篇關于插件這方面的文章,扯遠了(*^__^*) ……我們知道在支付寶的咻一咻頁面有個雷達擴散的動畫效果,當時感覺動畫效果非常棒,于是私下嘗試著實現(xiàn)了類似的效果,后來在github發(fā)現(xiàn)有大神也寫有類似效果,于是讀了一下大神的代碼發(fā)現(xiàn)我們的核心思想都是一樣的,只是細節(jié)不同,然后我就擇其善者而從之,把兩份代碼整合了一下......整合之后的運行效果如下所示:

開始講解實現(xiàn)之前我們先分析一下支付寶的咻一咻效果,進入支付寶咻一咻頁面后點擊了咻一咻按鈕,屏幕上先出現(xiàn)一個圓在不斷的進行放大操作,在該圓進行放大操作的同時其透明度也在由大到小進行變化,接著該圓沒有消失之前又會出現(xiàn)新的圓也在進行同樣的動畫操作……通過觀察我們發(fā)現(xiàn)這些圓都是按照固定的時間間隔在依次的執(zhí)行放大和透明度漸變的動畫操作,所以要實現(xiàn)同樣的效果,首先要有一個ViewGroup,然后給這個ViewGroup添加固定數(shù)量的子View,最后讓這些子View執(zhí)行放大和透明度漸變動畫就可以實現(xiàn)該效果了。清楚了這個大綱流程,實現(xiàn)起來就好辦了。
首先定義我們的ViewGroup,由于該ViewGroup僅僅是添加固定數(shù)量的子View,然后讓這些子View執(zhí)行一系列動畫,所以可以直接繼承FrameLayout,代碼如下所示:
public class RadarLayout extends FrameLayout {
public RadarLayout(Context context) {
super(context);
}
public RadarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RadarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
我們?yōu)樽约旱倪菀贿菪Ч丶∶麨镽adarLayout,radar為雷達的意思,RadarLayout就表示在不斷的進行掃描的意思。通過前邊的分析我們知道RadarLayout是由固定數(shù)量的子View組成的,因此RadarLayout需要有表示子View數(shù)量的屬性并且該屬性外界可訪問可修改;由于子View的執(zhí)行動畫是放縮和透明度漸變同時進行的,所以RadarLayout需要用動畫集來組裝各個動畫;由于動畫執(zhí)行時需要知道執(zhí)行時間所以RadarLayout需要有表示執(zhí)行時間的屬性并且該屬性外界可訪問可修改;由于RadarLayout的動畫效果是子View來執(zhí)行的,在咻一咻頁面是個圓,所以需要定義子View并畫在屏幕上,而畫在屏幕上需要有畫筆,畫筆需要有顏色,還需要知道畫在哪,所以可定義我們的RadarLayout定義如下所示:
public class RadarLayout extends FrameLayout {
public static final int INFINITE = 0;
private static final int DEFAULT_COUNT = 4;
private static final int DEFAULT_COLOR = Color.rgb(0, 116, 193);
private static final int DEFAULT_DURATION = 7000;
private static final int DEFAULT_REPEAT = INFINITE;
private static final int DEFAULT_STROKE_WIDTH = 2;
private int mCount;
private int mDuration;
private int mRepeat;
private AnimatorSet mAnimatorSet;
private Paint mPaint;
private int mColor;
private float mRadius;
private float mCenterX;
private float mCenterY;
private int mStrokeWidth;
private boolean mIsStarted;
private boolean mUseRing;
public RadarLayout(Context context) {
super(context);
initGlobalparams();
}
public RadarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initGlobalparams();
}
public RadarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initGlobalparams();
}
private void initGlobalparams() {
mColor = DEFAULT_COLOR;
mCount = DEFAULT_COUNT;
mDuration = DEFAULT_DURATION;
mRepeat = DEFAULT_REPEAT;
mUseRing = false;
mStrokeWidth = dip2px(DEFAULT_STROKE_WIDTH);
build();
}
public synchronized void start() {
if (mAnimatorSet == null || mIsStarted) {
return;
}
mAnimatorSet.start();
}
public synchronized void stop() {
if (mAnimatorSet == null || !mIsStarted) {
return;
}
mAnimatorSet.end();
}
public synchronized boolean isStarted() {
return (mAnimatorSet != null && mIsStarted);
}
public int getCount() {
return mCount;
}
public int getDuration() {
return mDuration;
}
public void setCount(int count) {
if (count < 0) {
throw new IllegalArgumentException("Count cannot be negative");
}
if (count != mCount) {
mCount = count;
reset();
invalidate();
}
}
public void setDuration(int millis) {
if (millis < 0) {
throw new IllegalArgumentException("Duration cannot be negative");
}
if (millis != mDuration) {
mDuration = millis;
reset();
invalidate();
}
}
public void setColor(int color) {
if (mColor != color) {
mColor = color;
reset();
invalidate();
}
}
public void setUseRing(boolean useRing) {
if (mUseRing != useRing) {
mUseRing = useRing;
reset();
invalidate();
}
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
// 確定圓的圓點坐標及半徑
mCenterX = width * 0.5f;
mCenterY = height * 0.5f;
mRadius = Math.min(width, height) * 0.5f;
}
private void clear() {
stop();
removeAllViews();
}
private void build() {
LayoutParams params = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
int repeatCount = (mRepeat == INFINITE) ? ObjectAnimator.INFINITE : mRepeat;
List<Animator> animators = new ArrayList<Animator>();
for (int index = 0; index < mCount; index++) {
RadarView radarView = new RadarView(getContext());
radarView.setScaleX(0);
radarView.setScaleY(0);
radarView.setAlpha(1);
addView(radarView, index, params);
// 計算時間間隔
long delay = index * mDuration / mCount;
// 屬性動畫
animators.add(create(radarView, "scaleX", repeatCount, delay, 0, 1));
animators.add(create(radarView, "scaleY", repeatCount, delay, 0, 1));
animators.add(create(radarView, "alpha", repeatCount, delay, 1, 0));
}
mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(animators);
mAnimatorSet.setInterpolator(new LinearInterpolator());
mAnimatorSet.setDuration(mDuration);
mAnimatorSet.addListener(mAnimatorListener);
}
private ObjectAnimator create(View target, String propertyName, int repeatCount, long delay, float from, float to) {
ObjectAnimator animator = ObjectAnimator.ofFloat(target, propertyName, from, to);
animator.setRepeatCount(repeatCount);
animator.setRepeatMode(ObjectAnimator.RESTART);
animator.setStartDelay(delay);
return animator;
}
private void reset() {
boolean isStarted = isStarted();
clear();
build();
if (isStarted) {
start();
}
}
private class RadarView extends View {
public RadarView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
if (null == mPaint) {
mPaint = new Paint();
mPaint.setColor(mColor);
mPaint.setAntiAlias(true);
// 注意Style的用法,【STROKE:畫環(huán)】【FILL:畫圓】
mPaint.setStyle(mUseRing ? Style.STROKE : Style.FILL);
mPaint.setStrokeWidth(mUseRing ? mStrokeWidth : 0);
}
// 畫圓或環(huán)
canvas.drawCircle(mCenterX, mCenterY, mUseRing ? mRadius - mStrokeWidth : mRadius, mPaint);
}
}
private int dip2px(float dpValue) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
mIsStarted = true;
}
@Override
public void onAnimationEnd(Animator animator) {
mIsStarted = false;
}
@Override
public void onAnimationCancel(Animator animator) {
mIsStarted = false;
}
@Override
public void onAnimationRepeat(Animator animator) {
}
};
}
我們的RadarLayout已經(jīng)完成了,代碼很簡單,相信小伙伴們都看的懂,需要注意屬性mUseRing的含義,當mUserRing為true時表示使用環(huán)形雷達脈沖,否則使用圓形雷達脈沖。其次是屬性動畫的使用,如果有不明白的請自行查閱,這里就不再多多介紹了。接下來編寫我們的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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.llew.wb.MainActivity" > <View android:id="@+id/holder" android:layout_width="@dimen/activity_horizontal_margin" android:layout_height="10dp" android:layout_centerHorizontal="true" /> <com.llew.wb.RadarLayout android:id="@+id/radarlayout1" android:layout_width="match_parent" android:layout_height="150dp" android:layout_toLeftOf="@id/holder" android:background="#bbaacc" > </com.llew.wb.RadarLayout> <com.llew.wb.RadarLayout android:id="@+id/radarlayout2" android:layout_width="match_parent" android:layout_height="150dp" android:layout_toRightOf="@id/holder" android:background="#bbaacc" > </com.llew.wb.RadarLayout> <com.llew.wb.RadarLayout android:id="@+id/radarlayout3" android:layout_width="match_parent" android:layout_height="150dp" android:layout_below="@id/radarlayout1" android:layout_marginTop="@dimen/activity_horizontal_margin" android:layout_toLeftOf="@id/holder" android:background="#bbaacc" > </com.llew.wb.RadarLayout> <com.llew.wb.RadarLayout android:id="@+id/radarlayout4" android:layout_width="match_parent" android:layout_height="150dp" android:layout_below="@id/radarlayout1" android:layout_marginTop="@dimen/activity_horizontal_margin" android:layout_toRightOf="@id/holder" android:background="#bbaacc" > </com.llew.wb.RadarLayout> <Button android:layout_width="100dp" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="20dp" android:onClick="start" android:text="開始" /> </RelativeLayout>
在activity_main.xml布局中我們添加了4個RadarLayout,目的是對比他們的差異,接下編寫我們的MainActivity,代碼如下:
public class MainActivity extends Activity {
private RadarLayout layout1;
private RadarLayout layout2;
private RadarLayout layout3;
private RadarLayout layout4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layout1 = (RadarLayout) findViewById(R.id.radarlayout1);
layout2 = (RadarLayout) findViewById(R.id.radarlayout2);
layout2.setUseRing(true);
layout2.setCount(2);
layout3 = (RadarLayout) findViewById(R.id.radarlayout3);
layout3.setUseRing(false);
layout3.setColor(Color.RED);
layout4 = (RadarLayout) findViewById(R.id.radarlayout4);
layout4.setCount(7);
layout4.setColor(Color.BLUE);
layout4.setUseRing(true);
}
public void start(View view) {
layout1.start();
layout2.start();
layout3.start();
layout4.start();
}
}
在MainActivity中我們設置了layout1為默認值效果,layout2設置了使用環(huán)形效果并且設置了數(shù)量為2個;layout3設置為使用圓形并設置圓形的顏色為紅色,layout3我們設置了使用環(huán)形,設置了環(huán)形數(shù)量為7個并設置顏色給藍色。當點擊了開始按鈕后,我們打開每一個RadarLayout的動畫,運行效果如下所示:

運行效果看起來還不錯,基本上實現(xiàn)了仿支付的咻一咻的雷達脈沖效果,主要原理就是利用了屬性動畫并把這些屬性動畫集合起來一塊播放即可。需要注意的是如果想在低版本兼容屬性動畫可以使用Jake Wharton大神開源的著名動畫兼容庫NineOldAndroids,最后感謝收看(*^__^*) ……
以上所述是小編給大家介紹的Android自定義ViewGroup實現(xiàn)絢麗的仿支付寶咻一咻雷達脈沖效果,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關文章
Android編程實現(xiàn)基于局域網(wǎng)udp廣播自動建立socket連接的方法
這篇文章主要介紹了Android編程實現(xiàn)基于局域網(wǎng)udp廣播自動建立socket連接的方法,涉及Android使用udp廣播實現(xiàn)socket通訊的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
Android 中TextView的使用imageview被壓縮問題解決辦法
這篇文章主要介紹了Android 中TextView的使用imageview被壓縮問題解決辦法的相關資料,需要的朋友可以參考下2017-04-04
Android仿拉手網(wǎng)團購App產(chǎn)品詳情界面效果
這篇文章主要介紹了Android仿拉手網(wǎng)團購App產(chǎn)品詳情界面效果,代碼簡單易懂,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-05-05
Android AlertDialog六種創(chuàng)建方式案例詳解
這篇文章主要介紹了Android AlertDialog六種創(chuàng)建方式案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08
Android RecyclerView的Item自定義動畫及DefaultItemAnimator源碼分析
這篇文章主要介紹了Android RecyclerView的Item自定義動畫及DefaultItemAnimator源碼,感興趣的小伙伴們可以參考一下2016-07-07

