本文由manymore13原创,转载请标明出处http://blog.csdn.net/manymore13/article/details/12907969
上一篇 Android特效开发(可伸缩View带互相挤压效果 )初级篇
在上一篇文章末尾我提出了三点不足 ,遂本篇主要是为了解决上篇的不足之处。
对于上一篇的不足之处 有三点 :
1. 特效动画死板,变化速度死板;
2. 特效动画不能设置动画时间,如遇到高分辨率的机型,动画时间会变长。
3. view只能水平伸缩,不能竖直伸缩。
对于第一点不足之处变化速度死板,我立马想到了Android中Interpolator类,对于做过Android中动画的同学
来说,这个类应该并不陌生,该类可以改变动画的变化速率,它的直接子类中有
BounceInterpolator 弹球效果
AccelerateInterpolator 加速
LinearInterpolator 匀速
更多子类可请查阅Android开发文档
它有个getInterpolation (float input) 方法,你可以传入动画消逝时间值(input范围 [0,1] ),0代表开始,1代表
结束,获取变化速率。等会儿代码中有用到这个类。
有关插值器可参考: android动画(一)Interpolator
对于第一二三点不足,我写了辅助类StretchAnimation可以解决。欢迎批评指正。
StretchAnimation只负责view水平拉伸或者垂直拉伸。你可以设置动画的时间,你可以设置它的插值器,改变动
画的效果。下面该类的实现过程。
public class StretchAnimation { private final static String TAG = "SizeChange"; private Interpolator mInterpolator; // 好多书上翻译为插值器 private View mView; // 你要伸缩的view private int mCurrSize; //当前大小 private int mRawSize; private int mMinSize; // 最小大小 固定值 private int mMaxSize; // 最大大小 固定值 private boolean isFinished = true;// 动画结束标识 private TYPE mType = TYPE.vertical; private final static int FRAMTIME = 20;// 一帧的时间 毫秒 public static enum TYPE { horizontal, // 改变view水平方向的大小 vertical // 改变view竖直方向的大小 } private int mDuration; // 动画运行的时间 private long mStartTime;// 动画开始时间 private float mDurationReciprocal; private int mDSize; // 需要改变view大小的增量 public StretchAnimation(int maxSize, int minSize, TYPE type, int duration) { if (minSize >= maxSize) { throw new RuntimeException("View的最大改变值不能小于最小改变值"); } mMinSize = minSize; mMaxSize = maxSize; mType = type; mDuration = duration; } public void setInterpolator(Interpolator interpolator) { mInterpolator = interpolator; } public TYPE getmType() { return mType; } public boolean isFinished() { return isFinished; } public void setDuration(int duration) { mDuration = duration; } private void changeViewSize() { if (mView != null && mView.getVisibility() != View.GONE) { LayoutParams params = mView.getLayoutParams(); if (mType == TYPE.vertical) { params.height = mCurrSize; } else if (mType == TYPE.horizontal) { params.width = mCurrSize; } Log.i(TAG, "CurrSize = " + mCurrSize + " Max=" + mMaxSize + " min=" + mMinSize); mView.setLayoutParams(params); } } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { if (!computeViewSize()) { mHandler.sendEmptyMessageDelayed(1, FRAMTIME); } else { if (animationlistener != null) { animationlistener.animationEnd(mView); } } } super.handleMessage(msg); } }; /** * @return 返回true 表示动画完成 */ private boolean computeViewSize() { if (isFinished) { return isFinished; } int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed <= mDuration) { float x = timePassed * mDurationReciprocal; if (mInterpolator != null) { x = mInterpolator.getInterpolation(x); } mCurrSize = mRawSize + Math.round(x * mDSize); } else { isFinished = true; mCurrSize = mRawSize + mDSize; } changeViewSize(); return isFinished; } public void startAnimation(View view) { if (view != null) { mView = view; } else { Log.e(TAG, "view 不能为空"); return; } LayoutParams params = mView.getLayoutParams(); if (isFinished) { mDurationReciprocal = 1.0f / (float) mDuration; if (mType == TYPE.vertical) { mRawSize = mCurrSize = mView.getHeight(); } else if (mType == TYPE.horizontal) { mRawSize = mCurrSize = mView.getWidth(); } Log.i(TAG, "mRawSize=" + mRawSize); if (mCurrSize > mMaxSize || mCurrSize < mMinSize) { throw new RuntimeException( "View 的大小不达标 currentViewSize > mMaxSize || currentViewSize < mMinSize"); } isFinished = false; mStartTime = AnimationUtils.currentAnimationTimeMillis(); // 动画开始时间 if (mCurrSize < mMaxSize) { mDSize = mMaxSize - mCurrSize; } else { mDSize = mMinSize - mMaxSize; } Log.i(TAG, "mDSize=" + mDSize); mHandler.sendEmptyMessage(1); } } private AnimationListener animationlistener; interface AnimationListener { public void animationEnd(View v); } public void setOnAnimationListener(AnimationListener listener) { animationlistener = listener; }
原理补充:每次开始播放动画时你要知道需要改变的值是多少,我上面是用mDSize表示,然后根据时间的消逝值除以你设置的动画要播放的时间值得到结果X,你再通过Interpolation.getInterpolation(x)就可以得到变化速率,变化速率乘以mDSize,就可以得到此时时的View大小改变量了。改变量晓得了,你就可以算出view的此时的大小了。
下面是我在activity中使用StretchAnimation的过程
public class StretchActivity extends Activity implements View.OnClickListener, StretchAnimation.AnimationListener { private final static String TAG = "StretchActivity"; // 屏幕宽度 private int screentWidth = 0; private int screentHeight = 0; // View可伸展最长的宽度 private int maxSize; // View可伸展最小宽度 private int minSize; // 当前点击的View private View currentView; // 显示最长的那个View private View preView; // 主布局ViewGroup private LinearLayout mainContain; private StretchAnimation stretchanimation; private TextView tvLog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mainContain = (LinearLayout) this.findViewById(R.id.main_contain); initCommonData(); initViewData(2); } /** * @param index 初始化时哪一个是最大的 从零开始 */ private void initViewData(int index) { tvLog = (TextView)this.findViewById(R.id.tv_log); View child; int sizeValue = 0; LayoutParams params = null; int childCount = mainContain.getChildCount(); if(index <0 || index >= childCount) { throw new RuntimeException("index 超出范围"); } for (int i = 0; i < childCount; i++) { child = mainContain.getChildAt(i); child.setOnClickListener(this); params = child.getLayoutParams(); if (i == index) { preView = child; sizeValue = maxSize; } else { sizeValue = minSize; } if(stretchanimation.getmType() == com.manymore13.Stretch.StretchAnimation.TYPE.horizontal){ params.width = sizeValue; }else if(stretchanimation.getmType() == com.manymore13.Stretch.StretchAnimation.TYPE.vertical){ params.height = sizeValue; } child.setLayoutParams(params); } } private void initCommonData() { DisplayMetrics metric = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metric); screentWidth = metric.widthPixels; // 屏幕宽度(像素) screentHeight= metric.heightPixels; // measureSize(screentHeight); stretchanimation = new StretchAnimation(maxSize, minSize, StretchAnimation.TYPE.vertical, 500); stretchanimation.setInterpolator(new BounceInterpolator()); stretchanimation.setDuration(800); stretchanimation.setOnAnimationListener(this); } /** * 测量View 的 max min 长度 这里你可以根据你的要求设置max * @param screenSize * @param index 从零开始 */ private void measureSize(int layoutSize) { int halfWidth = layoutSize / 2; maxSize = halfWidth - 50; minSize = (layoutSize - maxSize) / (mainContain.getChildCount() - 1); Log.i(TAG, "maxWidth="+maxSize+" minWidth = "+minSize); } @Override public void onClick(View v) { int id = v.getId(); View tempView = null; switch (id) { case R.id.btnOne: tempView = mainContain.getChildAt(0); break; case R.id.btnTwo: tempView = mainContain.getChildAt(1); break; case R.id.btnThree: tempView = mainContain.getChildAt(2); break; case R.id.btnFour: tempView = mainContain.getChildAt(3); break; } if(tempView == preView){ Log.d(TAG, ""); String addInfo = ((Button) tempView).getText().toString()+"动画不能执行"; printAddViewDebugInfo(addInfo); return; }else{ currentView = tempView; } Log.i(TAG, ((Button) currentView).getText().toString() + " click"); clickEvent(currentView); onOffClickable(false); String addInfo = ((Button) currentView).getText().toString()+"start animation"; printAddViewDebugInfo(addInfo); stretchanimation.startAnimation(currentView); } private void clickEvent(View view) { View child; int childCount = mainContain.getChildCount(); LinearLayout.LayoutParams params; for (int i = 0; i < childCount; i++) { child = mainContain.getChildAt(i); if (preView == child) { params = (android.widget.LinearLayout.LayoutParams) child .getLayoutParams(); if(preView != view){ params.weight = 1.0f; } child.setLayoutParams(params); } else { params = (android.widget.LinearLayout.LayoutParams) child .getLayoutParams(); params.weight = 0.0f; if(stretchanimation.getmType() == StretchAnimation.TYPE.horizontal){ params.width = minSize; }else if(stretchanimation.getmType() == StretchAnimation.TYPE.vertical){ params.height = minSize; } child.setLayoutParams(params); } } preView = view; } // 调试信息 private void printDebugMsg() { View child; int childCount = mainContain.getChildCount(); StringBuilder sb = new StringBuilder(); sb.append("preView = "+((Button)preView).getText().toString()+" "); sb.append("click = "+((Button)currentView).getText().toString()+" "); for (int i = 0; i < childCount; i++) { child = mainContain.getChildAt(i); LinearLayout.LayoutParams params = (android.widget.LinearLayout.LayoutParams) child .getLayoutParams(); sb.append(params.weight+" "); } Log.d(TAG, sb.toString()); } // LinearLayout下所有childView 可点击开关 // 当动画在播放时应该设置为不可点击,结束时设置为可点击 private void onOffClickable(boolean isClickable) { View child; int childCount = mainContain.getChildCount(); for (int i = 0; i < childCount; i++) { child = mainContain.getChildAt(i); child.setClickable(isClickable); } } @Override public void animationEnd(View v) { Log.i(TAG, ("-----"+((Button)v).getText().toString())+" annation end"); String addStr = ((Button)v).getText().toString()+" annation end"; printAddViewDebugInfo(addStr); onOffClickable(true); } private void printAddViewDebugInfo(String addinfo) { String temp = tvLog.getText().toString(); tvLog.setText(temp+"\n"+addinfo); }
在上面代码中可以看到stretchanimation 的初始化与调用
初始化 stretchanimation// 我这里设置的View是垂直伸缩动画,maxSIze是伸缩的最大值,minSize是伸缩的最小值,500是500毫秒的动画时间
// 注意:你这里设置StretchAnimation.TYPE.vertical垂直伸缩动画,你XML中相应View布局也应该是垂直,
stretchanimation = new StretchAnimation(maxSize, minSize, StretchAnimation.TYPE.vertical, 500); // 设置它的插值器 弹球效果 stretchanimation.setInterpolator(new BounceInterpolator()); // 动画播放的总时间 stretchanimation.setDuration(800); // 动画播放完后的回调 stretchanimation.setOnAnimationListener(this); // 播放动画 参数是你要播放的View stretchanimation.startAnimation(currentView)
下面是在模拟器上运行的效果图, 有点卡。我设置的动画时间是800毫秒,建议你在真机上玩玩看
不同插值器运行效果不一样,上面是垂直动画效果
下面我们只需简单的三步就可以实现水平效果
1. measureSize(screentWidth);你可以设置屏幕宽度,例如上面我这个大小设置的是屏幕的高度,所以四个按钮就占屏幕的高度。
2. StretchAnimation实例化时修改 StretchAnimation.TYPE.horizontal 水平效果
3. 修改XML布局Linearlayout属性 android:orientation="horizontal" 水平
修改后的水平动画效果:
本篇相对于上一篇来说算是加强版 。水平伸缩动画和垂直伸缩动画可轻松转换,相对于上一篇增加对动画的控制
功能。可以控制动画时间,而动画时间不会因分辨率的增加而改变;通过改变动画的速率可实现不同的动画效果,弹
球效果,加速,匀速效果等等。
对上述代码稍作修改就可以实现如下效果,这种效果用到插值器 AccelerateDecelerateInterpolator