在上一篇文章末尾我提出了三點不足 ,遂本篇主要是為了解決上篇的不足之處。
對於上一篇的不足之處 有三點 :
1. 特效動畫死板,變化速度死板;
2. 特效動畫不能設置動畫時間,如遇到高分辨率的機型,動畫時間會變長。
3. view只能水平伸縮,不能豎直伸縮。
對於第一點不足之處變化速度死板,我立馬想到了Android中Interpolator類,對於做過Android中動畫的同學
來說,這個類應該並不陌生,該類可以改變動畫的變化速率,它的直接子類中有
BounceInterpolator 彈球效果
AccelerateInterpolator 加速
LinearInterpolator 勻速
更多子類可請查閱Android開發文檔
它有個getInterpolation (float input) 方法,你可以傳入動畫消逝時間值(input范圍 [0,1] ),0代表開始,1代表
結束,獲取變化速率。等會兒代碼中有用到這個類。
有關插值器可參考: android動畫(一)Interpolator
對於第一二三點不足,我寫了輔助類StretchAnimation可以解決。歡迎批評指正。
StretchAnimation只負責view水平拉伸或者垂直拉伸。你可以設置動畫的時間,你可以設置它的插值器,改變動
畫的效果。下面該類的實現過程。
[java]
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的過程
[java]
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布局也應該是垂直,
[java]
stretchanimation = new StretchAnimation(maxSize, minSize, StretchAnimation.TYPE.vertical, 500);
// 設置它的插值器 彈球效果
stretchanimation.setInterpolator(new BounceInterpolator());
// 動畫播放的總時間
stretchanimation.setDuration(800);
// 動畫播放完後的回調
stretchanimation.setOnAnimationListener(this);
// 播放動畫 參數是你要播放的View
stretchanimation.startAnimation(currentView)