android自定義動(dòng)畫專題二
在上篇文章中給大家介紹了android自定義動(dòng)畫的第一種表現(xiàn)形式:view的繪制;不過這只是一種單純利用自定義控件繪制的方式去實(shí)現(xiàn);這篇文章會(huì)給大家演示如何通過自定義控件(測(cè)量乞娄,排版,繪制)+android原生動(dòng)畫的方式一起實(shí)現(xiàn)一些比較酷炫復(fù)雜的效果藤违。
1.衛(wèi)星菜單demo
該demo實(shí)現(xiàn)的主要核心技術(shù)點(diǎn)是:繼承ViewGroup實(shí)現(xiàn)子控件的測(cè)量谍咆,排版,以及通過Animation動(dòng)畫控制子控件的動(dòng)畫效果包个。
首先來看下該demo的效果展示:
看到效果圖后我們就要思考通過什么樣的方式才能實(shí)現(xiàn)這樣的效果刷允?不難看出該效果不僅僅只有一個(gè)控件而是由多個(gè)控件組成并且?guī)в袆?dòng)畫效果。那么從產(chǎn)品設(shè)計(jì)上來看碧囊,最左下角的加號(hào)控制按鈕位置一直沒有動(dòng)過树灶,只是做了一個(gè)旋轉(zhuǎn)操作;而其他五個(gè)飛出來的按鈕帶有功能特色可以具備點(diǎn)擊實(shí)現(xiàn)的操作糯而。并且在實(shí)際需求中很可能不止五個(gè)按鈕或者少于五個(gè)按鈕天通,又甚者按鈕的樣式改變了等等的需求變化,這些都是我們需要提前考慮進(jìn)來的歧蒋。所以土砂,針對(duì)該效果我們必須要自己定義一個(gè)ViewGroup容器來管理這些變化的子控件,對(duì)它們進(jìn)行需求的排版以及動(dòng)畫處理谜洽。
1)視圖效果的處理-布局
首先定義一個(gè)自定義控件ArcMenu去繼承ViewGroup,復(fù)寫構(gòu)造函數(shù)和onLayout()吴叶;不過先不著急實(shí)現(xiàn)具體的代碼阐虚,現(xiàn)在相當(dāng)于有了一個(gè)沒有任何規(guī)則的空的容器,那么我們可以先將布局造出來蚌卤,代碼如下:
<com.zphuan.animationproject.view.ArcMenu
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/rl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/composer_button" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_icn_plus" />
</RelativeLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/composer_camera" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/composer_music" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/composer_place" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/composer_sleep" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/composer_sun" />
</com.zphuan.animationproject.view.ArcMenu>
也就是將我們需要展示的六個(gè)按鈕控件引進(jìn)來了实束,其中第一個(gè)加號(hào)按鈕是由兩個(gè)圖片重疊顯示的,其余都是ImageView控件逊彭。
2)控件的測(cè)量
由于當(dāng)前自定義控件繼承ViewGroup,需要對(duì)子視圖做測(cè)量處理咸灿。代碼如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
child.measure(0, 0);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
這里并沒有對(duì)當(dāng)前控件設(shè)置測(cè)量寬高,而是走的super侮叮,根據(jù)布局文件中寫的寬高來決定整個(gè)控件的大小避矢。
3)視圖的擺放處理
這里的視圖擺放是一個(gè)重點(diǎn),也是一個(gè)難點(diǎn)囊榜,由于我們的其余子控件是圍繞著第一個(gè)子控件進(jìn)行排列的审胸,并且每個(gè)子控件距離第一個(gè)子控件的距離是相等的,所以這個(gè)地方最好是畫圖思考推算每一個(gè)子控件的左上右下卸勺,否則很容易思路就亂掉了砂沛,圖示如下:
這里我們默認(rèn)圖形初始化擺放的時(shí)候就是展開的。
具體代碼如下:
/**
* 此數(shù)據(jù)為每個(gè)子視圖距離第一個(gè)控件的距離
*/
private static final int RADIUS = (int) MyApplication.getContext().getResources().getDimension(R.dimen.ArcMenu_Radius);
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
child0 = getChildAt(0);
child0.setOnClickListener(this);
//將第一個(gè)控件擺放在左下角
child0.layout(0, height - child0.getMeasuredHeight(), child0.getMeasuredWidth(), height);
int count = getChildCount();
for (int i = 0; i < count - 1; i++) {
double angle = Math.PI / 2 / (count - 2) * i;
View child = getChildAt(i + 1);
int left = (int) (child0.getLeft() + RADIUS * Math.sin(angle));
int top = (int) (child0.getTop() - RADIUS * Math.cos(angle));
int right = left + child.getMeasuredWidth();
int bottom = top + child.getMeasuredHeight();
child.layout(left, top, right, bottom);
child.setVisibility(INVISIBLE);
}
}
4)控件的平移和旋轉(zhuǎn)
最后就是實(shí)現(xiàn)控件的平移和旋轉(zhuǎn)曙求,這里旋轉(zhuǎn)就不說了很簡(jiǎn)單圍繞自身控件的中心機(jī)型旋轉(zhuǎn)360或720就行碍庵。主要是平移動(dòng)畫映企,這里使用相對(duì)自身和相對(duì)parent 都不好處理,最好的方式就是使用 Animation.ABSOLUTE 絕對(duì)值的方式静浴,不過要注意的是這里的絕對(duì)值是指的動(dòng)畫作用于的當(dāng)前控件的坐標(biāo)系為基準(zhǔn)堰氓。最后,每個(gè)控件在平移到指定的位置后都會(huì)有個(gè)超出一段距離再歸位的效果马绝,這也是用的動(dòng)畫自帶的插值器OvershootInterpolator()實(shí)現(xiàn)豆赏。
具體代碼如下:
@Override
public void onClick(View v) {
//1.旋轉(zhuǎn)自身
rotateChild0(v);
//2.執(zhí)行其他子視圖的動(dòng)畫
animateOtherChild();
}
private void animateOtherChild() {
int count = getChildCount();
for (int i = 0; i < count - 1; i++) {
AnimationSet as = new AnimationSet(true);
final View child = getChildAt(i + 1);
child.setVisibility(View.VISIBLE);
int left = child.getLeft();
TranslateAnimation ta;
if(status==CurrentStatus.CLOSE) {
ta = new TranslateAnimation(
Animation.ABSOLUTE, -left,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, child0.getBottom() - child.getBottom(), Animation.ABSOLUTE, 0);
}else{
ta = new TranslateAnimation(
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, -left,
Animation.ABSOLUTE, 0, Animation.ABSOLUTE, child0.getBottom() - child.getBottom());
}
ta.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
Log.i(TAG, "onAnimationEnd");
if(status==CurrentStatus.CLOSE){
child.clearAnimation();
child.setVisibility(View.INVISIBLE);
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
ta.setStartOffset(200*i);
ta.setDuration(2000);
ta.setInterpolator(new OvershootInterpolator());
RotateAnimation ra = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
ra.setDuration(2000);
as.addAnimation(ra);
as.addAnimation(ta);
as.setFillAfter(true);
child.startAnimation(as);
}
changeCurrentStatus();
}
private void rotateChild0(View v) {
RotateAnimation ra = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
ra.setDuration(1000);
v.startAnimation(ra);
}
private CurrentStatus status = CurrentStatus.CLOSE;
//定義衛(wèi)星菜單打開和關(guān)閉的狀態(tài)
public enum CurrentStatus {
OPEN, CLOSE;
}
private void changeCurrentStatus() {
status = status == CurrentStatus.CLOSE ? CurrentStatus.OPEN : CurrentStatus.CLOSE;
}
以上就是該案例的整個(gè)思路分析和具體的代碼展示,如果想要參考完整的代碼請(qǐng)到GitHub下載富稻,歡迎star和fork
https://github.com/zphuanlove/AnimationProject
2.屬性動(dòng)畫demo
以下的幾個(gè)動(dòng)畫效果也都是通過自定義控件繪制加屬性動(dòng)畫的方式去實(shí)現(xiàn)的掷邦。
圓形縮放效果:
在ondraw方法中繪制圓,通過ValueAnimator實(shí)現(xiàn)圓的放大和透明漸變
線條的縮放效果:
在ondraw()方法中繪制線條椭赋,通過ValueAnimator動(dòng)畫來平移和縮放畫布的方式控制線條
以上的效果就不在此處貼出代碼了抚岗,可以到我的GitHub去下載觀看更多的效果實(shí)現(xiàn),也歡迎大家start和fork哪怔。
https://github.com/zphuanlove/AnimationProject
3.奔跑吧宣蔚,汽車Demo
最后這個(gè)demo也是一個(gè)比較有意思的demo,一輛小汽車在馬路上奔馳认境,來看下效果:
1)主界面的布局
代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@drawable/backgroud"
android:layout_width="match_parent" android:layout_height="match_parent">
<FrameLayout
android:layout_width="277.33dp"
android:layout_height="334.66dp"
android:layout_gravity="center"
android:background="@drawable/water">
<FrameLayout
android:layout_marginTop="30dp"
android:layout_width="210dp"
android:layout_height="210dp"
android:background="@drawable/rain_bow"
android:layout_gravity="center_horizontal">
<com.zphuan.animationproject.view.Road
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<include layout="@layout/car"/>
</FrameLayout>
</FrameLayout>
</FrameLayout>
小汽車的組成也是由一個(gè)車殼加兩個(gè)輪子和一起尾氣組成胚委,這里的兩個(gè)輪子是由兩張圖片實(shí)現(xiàn)的一個(gè)幀動(dòng)畫。布局代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="26dp">
<ImageView
android:id="@+id/car_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/car_gas"
android:paddingBottom="7dp"
android:src="@drawable/car_body"/>
<ImageView
android:id="@+id/car_gas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/car_body"
android:src="@drawable/car_gas"
android:layout_marginBottom="8dp"/>
<ImageView
android:id="@+id/car_front_tire"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/car_front_tire"
android:layout_alignBottom="@id/car_body"
android:layout_alignRight="@id/car_body"
android:layout_marginRight="7dp"
/>
<ImageView
android:id="@+id/car_back_tire"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/car_back_tire"
android:layout_alignBottom="@id/car_body"
android:layout_toLeftOf="@id/car_front_tire"
android:layout_marginRight="16dp"/>
</RelativeLayout>
2)路的處理
繪制路是這一塊的難點(diǎn)叉信,首先路的圖片本身是方形的而且是一個(gè)長(zhǎng)圖亩冬,但是實(shí)際看到的效果是路在不停的移動(dòng)而且感覺永遠(yuǎn)移不完,并且是裁剪過的圓形效果硼身,所以這里路的處理最好單獨(dú)抽出來做成一個(gè)自定義控件硅急。
路不斷移動(dòng)的核心思路就是通過canvas的drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,@Nullable Paint paint)來實(shí)現(xiàn),bitmap指的是路這張圖佳遂,src指定的是從原圖路上摳出來的矩形部分营袜,dst指定的是實(shí)際繪制出來的矩形大小丑罪;所以想要讓路不停的移動(dòng)就是不斷的控制從原圖上摳出來的矩形區(qū)域荚板,用一個(gè)偏移量offset來控制src的左和右,如果超出了整個(gè)路的位置,則繪制路盡頭剩余的視圖+路開始的部分視圖巍糯。
具體代碼如下:
/**
* Created by PeiHuan on 2017/6/28.
*/
public class Road extends View {
private static final String TAG = "huan";
private Paint paint;
private Bitmap bitmap;
private int roadWidth;
public Road(Context context) {
this(context, null);
}
public Road(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public Road(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private Rect src;
private Rect dst;
private Path path;
private Rect lastSrc;
private Rect lastDst;
private void init() {
paint = new Paint();
src = new Rect();
dst = new Rect();
lastSrc = new Rect();
lastDst = new Rect();
path = new Path();
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.road);
roadWidth = bitmap.getWidth();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//Path.Direction.CW:順時(shí)針方向
path.addCircle(w / 2, h / 2, w / 2, Path.Direction.CW);
}
private int offset;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//畫布的剪切:可以按照path去剪切出對(duì)應(yīng)的圖形
canvas.clipPath(path);
if (offset + getWidth() <= roadWidth) {
src.set(offset, 0, getWidth() + offset, getHeight());
dst.set(0, 0, getWidth(), getHeight());
canvas.drawBitmap(bitmap, src, dst, paint);
} else {
src.set(offset, 0, roadWidth, getHeight());
dst.set(0, 0, src.width(), getHeight());
canvas.drawBitmap(bitmap, src, dst, paint);
lastSrc.set(0, 0, getWidth() - src.width(), getHeight());
lastDst.set(dst.width(), 0, getWidth(), getHeight());
canvas.drawBitmap(bitmap, lastSrc, lastDst, paint);
}
offset += 3;
offset %= roadWidth;
Log.i(TAG, "offset: " + offset);
invalidate();
}
}
3)輪子和尾氣的動(dòng)畫
輪子之前說過是用的幀動(dòng)畫啸驯,所以只要在activity中找到對(duì)應(yīng)的控件開啟動(dòng)畫即可,尾氣一閃一現(xiàn)的效果可以通過handler實(shí)現(xiàn)一個(gè)死循環(huán)不停的隱藏和顯示尾氣控件即可祟峦。
/**
* Created by PeiHuan on 2017/6/28.
*/
public class CarActivity extends Activity {
private ImageView gas;
private Handler handler = new Handler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_car);
startTireAnimation();
gas = (ImageView) findViewById(R.id.car_gas);
handler.postDelayed(new MyRunnable(),300);
}
private void startTireAnimation() {
ImageView front= (ImageView) findViewById(R.id.car_front_tire);
AnimationDrawable frontAnimationDrawable= (AnimationDrawable) front.getDrawable();
frontAnimationDrawable.start();
ImageView back= (ImageView) findViewById(R.id.car_back_tire);
AnimationDrawable backAnimationDrawable= (AnimationDrawable) back.getDrawable();
backAnimationDrawable.start();
}
private boolean isGasVisible = false;
private class MyRunnable implements Runnable {
@Override
public void run() {
gas.setVisibility(isGasVisible?View.INVISIBLE:View.VISIBLE);
isGasVisible = !isGasVisible;
handler.postDelayed(this,300);
}
}
}
具體的詳情代碼請(qǐng)參考GitHub:
https://github.com/zphuanlove/AnimationProject
總結(jié)
那么通過以上幾個(gè)案例效果給大家演示了自定義動(dòng)畫的第二種表現(xiàn)形式:即通過 自定義控件(View,ViewGroup)+原生動(dòng)畫(Animation動(dòng)畫罚斗,屬性動(dòng)畫,幀動(dòng)畫) 的方式實(shí)現(xiàn)宅楞。
Thanks针姿!