Android UI设计之自定义ViewGroup,实现绚丽的仿支付宝咻一咻雷达脉冲效果

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52801147

        去年春节的时候支付宝推行的集福娃活动着实火的不能再火了,更给力的是春晚又可以全民参与咻一咻集福娃活动,集齐五福就可平分亿元大红包,只可惜没有敬业福……那时候在家没事写了个咻一咻插件,只要到了咻一咻的时间点插件就可以自动的点击咻一咻来咻红包,当时只是纯粹练习这部分技术代码没有公开,后续计划写篇关于插件这方面的文章,扯远了(*^__^*) ……我们知道在支付宝的咻一咻页面有个雷达扩散的动画效果,当时感觉动画效果非常棒,于是私下尝试着实现了类似的效果,后来在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<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:画环】【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布局文件,如下所示:

<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设置了使用环形效果并且设置了数量为2个;layout3设置为使用圆形并设置圆形的颜色为红色,layout3我们设置了使用环形,设置了环形数量为7个并设置颜色给蓝色。当点击了开始按钮后,我们打开每一个RadarLayout的动画,运行效果如下所示:

        运行效果看起来还不错,基本上实现了仿支付的咻一咻的雷达脉冲效果,主要原理就是利用了属性动画并把这些属性动画集合起来一块播放即可。需要注意的是如果想在低版本兼容属性动画可以使用Jake Wharton大神开源的著名动画兼容库NineOldAndroids,最后感谢收看(*^__^*) ……


        源码下载




发布了39 篇原创文章 · 获赞 87 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/llew2011/article/details/52801147