Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android UI設計之(十四)自定義ViewGroup,實現絢麗的仿支付寶咻一咻雷達脈沖效果

Android UI設計之(十四)自定義ViewGroup,實現絢麗的仿支付寶咻一咻雷達脈沖效果

編輯:關於Android編程

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

\

開始講解實現之前我們先分析一下支付寶的咻一咻效果,進入支付寶咻一咻頁面後點擊了咻一咻按鈕,屏幕上先出現一個圓在不斷的進行放大操作,在該圓進行放大操作的同時其透明度也在由大到小進行變化,接著該圓沒有消失之前又會出現新的圓也在進行同樣的動畫操作……通過觀察我們發現這些圓都是按照固定的時間間隔在依次的執行放大和透明度漸變的動畫操作,所以要實現同樣的效果,首先要有一個ViewGroup,然後給這個ViewGroup添加固定數量的子View,最後讓這些子View執行放大和透明度漸變動畫就可以實現該效果了。清楚了這個大綱流程,實現起來就好辦了。

首先定義我們的ViewGroup,由於該ViewGroup僅僅是添加固定數量的子View,然後讓這些子View執行一系列動畫,所以可以直接繼承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);
    }
}
我們為自己的咻一咻效果控件取名為RadarLayout,radar為雷達的意思,RadarLayout就表示在不斷的進行掃描的意思。通過前邊的分析我們知道RadarLayout是由固定數量的子View組成的,因此RadarLayout需要有表示子View數量的屬性並且該屬性外界可訪問可修改;由於子View的執行動畫是放縮和透明度漸變同時進行的,所以RadarLayout需要用動畫集來組裝各個動畫;由於動畫執行時需要知道執行時間所以RadarLayout需要有表示執行時間的屬性並且該屬性外界可訪問可修改;由於RadarLayout的動畫效果是子View來執行的,在咻一咻頁面是個圓,所以需要定義子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 animators = new ArrayList();
        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:畫環】【FILL:畫圓】
        		mPaint.setStyle(mUseRing ? Style.STROKE : Style.FILL);
		        mPaint.setStrokeWidth(mUseRing ? mStrokeWidth : 0);
        	}
        	// 畫圓或環
        	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已經完成了,代碼很簡單,相信小伙伴們都看的懂,需要注意屬性mUseRing的含義,當mUserRing為true時表示使用環形雷達脈沖,否則使用圓形雷達脈沖。其次是屬性動畫的使用,如果有不明白的請自行查閱,這裡就不再多多介紹了。接下來編寫我們的activity_main.xml布局文件,如下所示:



    

    
    

    
    

    
    

    
    
在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設置了使用環形效果並且設置了數量為2個;layout3設置為使用圓形並設置圓形的顏色為紅色,layout3我們設置了使用環形,設置了環形數量為7個並設置顏色給藍色。當點擊了開始按鈕後,我們打開每一個RadarLayout的動畫,運行效果如下所示:

 

\

運行效果看起來還不錯,基本上實現了仿支付的咻一咻的雷達脈沖效果,主要原理就是利用了屬性動畫並把這些屬性動畫集合起來一塊播放即可。需要注意的是如果想在低版本兼容屬性動畫可以使用Jake Wharton大神開源的著名動畫兼容庫Androids" target="_blank">NineOldAndroids,最後感謝收看(*^__^*) ……

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved