上一篇 Android特效開發(可伸縮View帶互相擠壓效果 )初級篇
在上一篇文章末尾我提出了三點不足 ,遂本篇主要是爲了解決上篇的不足之處。
對於上一篇的不足之處 有三點 :
1. 特效動畫死板,變化速度死板;
2. 特效動畫不能設置動畫時間,如遇到高分辨率的機型,動畫時間會變長。
3. view只能水平伸縮,不能豎直伸縮。
對於第一點不足之處變化速度死板,我立馬想到了Android中Interpolator類,對於做過Android中動畫的同學
來說,這個類應該並不陌生,該類可以改變動畫的變化速率,它的直接子類中有
BounceInterpolator 彈球效果
更多子類可請查閱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;
- }
初始化該類後再調用startAnimation 就可以播放動畫。
原理補充:每次開始播放動畫時你要知道需要改變的值是多少,我上面是用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) currentView).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