前奏说明:昨天学习了一遍关于实现卫星导航菜单的博客,实现的动画使用的Tween动画,于是打算使用属性动画实现下同样的效果。
关于属性动画,属性动画通过改变view的属性来达到的动画效果,所以在本自定义view中 在动画结束后,还得开启一个新的动画换成之前的view属性
###新建自定义类继承ViewGroup
###新建自定义view中所需要的属性
res/values下面创建attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="position">
<enum name="left_top" value="0" />
<enum name="right_top" value="1" />
<enum name="left_bottom" value="2" />
<enum name="right_bottom" value="3" />
</attr>
<attr name="raduis" format="dimension" />
<declare-styleable name="ArtNavView" >
<attr name="position" />
<attr name="raduis" />
</declare-styleable>
</resources>
###layout新建自定义view布局
<?xml version="1.0" encoding="utf-8"?>
<com.example.test.myviews.ArtNavView
xmlns:android="sss"
xmlns:android="http://schemas.android.com/apk/res
/android"
xmlns:janecer="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
janecer:position="left_bottom"
janecer:raduis="200dp"
>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/composer_button"
android:padding="0dp"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/composer_icn_plus"
/>
</FrameLayout>
<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_thought"
/>
<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"
/>
</com.example.test.myviews.ArtNavView>
###自定义类中资源获取及动画的实现
###在定义View中获取声明的自定义属性
直接上代码吧
package com.example.test.myviews;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import com.example.test.R;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
/**
* author:janecer on 2016/1/15 13:54
* email:janecer@sina.cn
*/
public class ArtNavView extends ViewGroup implements View.OnClickListener{
private static final String TAG = ArtNavView.class.getSimpleName() ;
//导航按钮
private View mNavButton ;
//导航菜单的位置
private POSITION position = POSITION.LEFT_TOP ;
//导航菜单展开的半径
private float raduis ;
//卫星导航菜单是否是展开
private boolean isOpen = false ;
//给菜单一个点击事件差的限制
private long lastMenuItemClickTime ;
enum POSITION{
LEFT_TOP,RIGHT_TOP,LEFT_BOTTOM,RIGHT_BOTTOM ;
}
//卫星菜单被点击时的回调
private OnMenuItemClickListenre onMenuItemClickListenre ;
public interface OnMenuItemClickListenre {
/**
* @param view 被点击的菜单view
* @param position 被点击菜单的索引下标
*/
void onclick (View view,int position) ;
}
public void setOnMenuItemClickListenre(OnMenuItemClickListenre onMenuItemClickListenre) {
this.onMenuItemClickListenre = onMenuItemClickListenre ;
}
public ArtNavView(Context context) {
this(context, null );
}
public ArtNavView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* 当在布局文件中使用此view时,将调用此构造方法
* @param context
* @param attrs
* @param defStyleAttr
*/
public ArtNavView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
raduis = TypedValue.applyDimension(TypedValue.TYPE_DIMENSION, 120, getResources().getDisplayMetrics()) ;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ArtNavView, defStyleAttr, 0) ;
raduis = typedArray.getDimension(typedArray.getIndex(R.styleable.ArtNavView_raduis) ,raduis) ;
switch (typedArray.getInt(typedArray.getIndex(R.styleable.ArtNavView_position), -1)) {
case 0 :
position = POSITION.LEFT_TOP ;
break;
case 1 :
position = POSITION.RIGHT_TOP ;
break;
case 2 :
position = POSITION.LEFT_BOTTOM ;
break;
case 3 :
position = POSITION.RIGHT_BOTTOM ;
break;
}
Log.i(TAG , "position : " + position +" radus:" +raduis) ;
typedArray.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int size = getChildCount() ;
for (int i = 0 ;i<size ; i++) {
if( i == 0){
mNavButton = getChildAt(0) ;
mNavButton.setOnClickListener(this);
} else {
final View childView = getChildAt(i) ;
final int j = i ;
childView .setVisibility(View.INVISIBLE);
childView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (null != onMenuItemClickListenre) {
onMenuItemClickListenre.onclick(childView, j);
}
onMenuItemClickAnimation(childView,j) ;
}
});
}
measureChild(getChildAt(i) ,widthMeasureSpec,heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(changed){
int size = getChildCount() ;
View child ;
int l1 = 0 ,t1 = 0 ;
for (int i = 0 ;i< size ; i++) {
child = getChildAt(i) ;
switch (position){
case LEFT_TOP:
l1 = 0 ;
t1 = 0 ;
break ;
case RIGHT_TOP:
l1 = getMeasuredWidth() - child.getMeasuredWidth() ;
t1 = 0 ;
break ;
case LEFT_BOTTOM:
l1 = 0 ;
t1 = getMeasuredHeight() - child.getMeasuredHeight() ;
break ;
case RIGHT_BOTTOM:
l1 = getMeasuredWidth() - child.getMeasuredWidth() ;
t1 = getMeasuredHeight() - child.getMeasuredHeight() ;
break ;
}
child.layout(l1, t1, l1 + child.getMeasuredWidth(), t1 + child.getMeasuredHeight());
}
}
}
@Override
public void onClick(View v) {
if( v == mNavButton){
if (System.currentTimeMillis() - lastMenuItemClickTime > 300){ //压力测试,防止狂点 导致的一些问题,防止一些动画没有执行完毕又开始新的动画
toggle(true);
}
}
}
ObjectAnimator animator1 ;
ObjectAnimator animator2 ;
ObjectAnimator animator3;
public void onMenuItemClickAnimation(final View view,int position){
int size = getChildCount() ;
for(int i = 0 ; i < getChildCount() -1 ; i ++ ){
if (i+1 == position){
showItemClickAnim(view);
} else {//其它menuItem直接用透明属性动画隐藏
if(i != 0){
ObjectAnimator.ofFloat(getChildAt(i+1),"alpha" ,1.0f,0f).start() ;
}
}
}
lastMenuItemClickTime = System.currentTimeMillis() ;
}
/**
* 点击menuItem某一项时开始的动画,
* @param view
*/
private void showItemClickAnim(View view) {
animator1 = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, 2.0f) ;
animator2 = ObjectAnimator.ofFloat(view,"scaleY" ,1.0f,2.0f) ;
animator3 = ObjectAnimator.ofFloat(view,"alpha" ,1.0f,0f) ;
final AnimatorSet animatorSet = new AnimatorSet() ;
animatorSet.play(animator1).with(animator2) ;
animatorSet.play(animator2).with(animator3) ;
animatorSet.start();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
toggle(false);
}
});
}
/**
* 点击菜单
*/
private void toggle(boolean isShowBackAn) {
int l = 0 , t = 0 ;
int size = getChildCount();
Log.i(TAG, "clicked nav" + " size : " + size) ;
for (int i = 0 ; i < size - 1 ; i++){
l = (int)(raduis * Math.sin(Math.PI / 2 / (size - 2) * i)) ;
t = (int)(raduis * Math.cos(Math.PI/2/(size - 2) * i)) ;
startOpenOrClose(getChildAt(i+1), l, t,isShowBackAn) ;
}
isOpen = !isOpen ;
}
/**
* 设置并且开始每个菜单的动画菜单
* @param childAt
* @param l
* @param t
* @param isShowBackAn
*/
private void startOpenOrClose(final View childAt, int l, int t,final boolean isShowBackAn) {
Log.i(TAG, "l : " + l + " t : " + t) ;
if(isShowBackAn){
toggleNavAnimation();
}
ObjectAnimator obx = null ;
ObjectAnimator oby = null ;
ObjectAnimator obr = null ;
ObjectAnimator obScalx =null , obScaly = null ;
int startX = 0 ,startY = 0 ;
switch (position){
case LEFT_TOP:
break ;
case RIGHT_TOP:
startX = getMeasuredWidth() - childAt.getMeasuredWidth() ;
l = (getMeasuredWidth() - l - childAt.getMeasuredWidth()) ;
break ;
case LEFT_BOTTOM:
startY = getMeasuredHeight() - childAt.getMeasuredHeight() ;
t = (getMeasuredHeight() - t - childAt.getMeasuredHeight()) ;
break ;
case RIGHT_BOTTOM:
l = (getMeasuredWidth() - l - childAt.getMeasuredWidth()) ;
t = (getMeasuredHeight() - t - childAt.getMeasuredHeight()) ;
startX = getMeasuredWidth() - childAt.getMeasuredWidth() ;
startY = getMeasuredHeight() - childAt.getMeasuredHeight() ;
break ;
}
AnimatorSet animatorSet = new AnimatorSet() ;
if(isOpen){
if(isShowBackAn) {
obx = ObjectAnimator.ofFloat(childAt, "x", l, startX) ;
oby = ObjectAnimator.ofFloat(childAt, "y", t, startY) ;
obr = ObjectAnimator.ofFloat(childAt, "rotation", 720, 0) ;
} else {
obx = ObjectAnimator.ofFloat(childAt, "x",startX) ;
obx.setDuration(10) ;
oby = ObjectAnimator.ofFloat(childAt, "y", startY) ;
oby.setDuration(10) ;
obr = ObjectAnimator.ofFloat(childAt, "rotation", 720, 0) ;
obr.setDuration(10) ;
}
obx.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
childAt.setVisibility(INVISIBLE);
}
});
} else {
childAt.setVisibility(VISIBLE);
obx = ObjectAnimator.ofFloat(childAt, "x", startX, l) ;
oby = ObjectAnimator.ofFloat(childAt, "y", startY, t) ;
obr = ObjectAnimator.ofFloat(childAt, "rotation", 0, 720) ;
}
animatorSet.play(obx).with(oby) ;
animatorSet.play(oby).with(obr) ;
animatorSet.start();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if(!isShowBackAn){
AnimatorSet as = new AnimatorSet();
as.play(ObjectAnimator.ofFloat(childAt,"scaleX" ,1.0f)).with(ObjectAnimator.ofFloat(childAt, "scaleY", 1.0f)).with(ObjectAnimator.ofFloat(childAt, "alpha", 1.0f)) ;
as.setDuration(10) ;
as.start();
}
}
});
}
/**
* 点击 导航按钮开始的旋转动画
*/
private void toggleNavAnimation(){
int startD = 0 ;
int endD = 0 ;
if(isOpen){
startD = 360 ;
endD = 0 ;
} else {
startD = 0 ;
endD = 360 ;
}
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mNavButton, "rotation", startD, endD) ;
objectAnimator.start();
}
}
###测试