你知道android中的視差特效嗎
阻尼效果(視差特效)
空間,微博很多地方都有這種下拉出現的”阻尼“效果,這種效果最早在ios上出現,如今android上這種功能也是很常見了。
先看效果圖:
該功能可以分為兩個點:
- 當ListView下拉的時候,頂部的HeaderView會有一個拉長的效果;
- 當下拉一段距離後,ListView會復位,執行一個簡單的回彈動畫。
這個功能實現起來挺簡單的,下面來介紹如何實現:
第一個功能:(阻尼效果)
來看下關鍵程式碼:
@Override protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { Log.d("TAG", "deltaY: " +deltaY + " scrollY: " + scrollY + " scrollRangeY: " + scrollRangeY + " maxOverScrollY: " + maxOverScrollY + " isTouchEvent: " + isTouchEvent); // 手指拉動 並且 是下拉 if(isTouchEvent && deltaY < 0) { // 把拉動的瞬時變化量的絕對值交給Header, 就可以實現放大效果 if(mImage.getHeight() <= drawableHeight ) { //①將deltaY除以3使產生阻尼效果 int newHeight = mImage.getHeight() + Math.abs(deltaY/3.0f); mImage.getLayoutParams().height = newHeight; 使View重繪 mImage.requestLayout(); } } return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); }
看到上面的程式碼,該段邏輯主要針對第一個功能,要重寫overScrollBy方法,通過監聽下拉的移動的高度賦給ListView的HeaderView,使View重繪來實現這種效果。
注意上面的①處,將下拉的幅度按比例的縮小來賦值給圖片的高度時,就會產生很難下拉的效果,即“阻尼”效果。
第二個功能:(回彈動畫)
當手指鬆開時,需要執行回彈動畫。手指鬆開,即ACTION_UP,重寫onTouchEvent來實現相關邏輯。
看如下程式碼:
@Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_UP: // 執行回彈動畫, 方式一: 屬性動畫\值動畫 // 從當前高度mImage.getHeight(), 執行動畫到原始高度mOriginalHeight final int startHeight = mImage.getHeight(); final int endHeight = mOriginalHeight; // valueAnimator(startHeight, endHeight); // 執行回彈動畫, 方式二: 自定義Animation ResetAnimation animation = new ResetAnimation(mImage, startHeight, endHeight); startAnimation(animation); break; } return super.onTouchEvent(ev); } private void valueAnimator(final int startHeight, final int endHeight) { ValueAnimator mValueAnim = ValueAnimator.ofInt(1); mValueAnim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator mAnim) { //①獲取動畫的百分比 float fraction = mAnim.getAnimatedFraction(); // percent 0.0 -> 1.0 Log.d(TAG, "fraction: " +fraction); Integer newHeight = evaluate(fraction, startHeight, endHeight); mImage.getLayoutParams().height = newHeight; mImage.requestLayout(); } }); //設定動畫的插值器,OvershootInterpolator向前甩一定值後再回到原來位置 mValueAnim.setInterpolator(new OvershootInterpolator()); mValueAnim.setDuration(500); mValueAnim.start(); } public Integer evaluate(float fraction, Integer startValue, Integer endValue) { int startInt = startValue; return (int)(startInt + fraction * (endValue - startInt)); }
看到上面的邏輯,在onTouchEvent監聽到ACTION_UP後,需要執行回彈動畫,這部分既可以用屬性動畫來實現,也可以自定義動畫的方式來實現。
這兩種方式都是需要將ListView重置為初始狀態,不過是以動畫的方式來實現。
注意上面①處的程式碼,float fraction = mAnim.getAnimatedFraction();getAnimatedFraction的API解釋如下:
Returns the current animation fraction, which is the elapsed/interpolated fraction used in the most recent frame update on the animation.
返回當前動畫的百分比,這個百分比能夠在動畫執行中用於更新當前的片段。
自定義動畫的方式
上面除了採用屬性動畫外,還可以使用自定義動畫的方式。
看下自定義動畫的邏輯:
package com.wind.parallax.ui;
import android.view.animation.Animation;
import android.view.animation.OvershootInterpolator;
import android.view.animation.Transformation;
import android.widget.ImageView;
public class ResetAnimation extends Animation {
private final ImageView mImage;
private final int startHeight;
private final int endHeight;
public ResetAnimation(ImageView mImage, int startHeight, int endHeight) {
this.mImage = mImage;
this.startHeight = startHeight;
this.endHeight = endHeight;
setInterpolator(new OvershootInterpolator());
//設定動畫執行時長
setDuration(500);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
// interpolatedTime 0.0f -> 1.0f
Integer newHeight = evaluate(interpolatedTime, startHeight, endHeight);
mImage.getLayoutParams().height = newHeight;
mImage.requestLayout();
super.applyTransformation(interpolatedTime, t);
}
/**
* 型別估值器
* @param fraction
* @param startValue
* @param endValue
* @return
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
該自定義動畫就是將最上面的屬性動畫改寫下,這部分的邏輯挺簡單的,就不再重複解釋了。
補充
最後看下MainActivity和佈局中的邏輯。
MainActivity.java
package com.wind.parallax;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import com.wind.parallax.ui.MyListView;
import com.wind.parallax.utils.Cheeses;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyListView mListView = (MyListView) findViewById(R.id.lv);
final View mHeaderView = View.inflate(MainActivity.this, R.layout.view_header, null);
final ImageView mImage = (ImageView) mHeaderView.findViewById(R.id.iv);
mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressLint("NewApi")
@Override
public void onGlobalLayout() {
// 當佈局填充結束之後, 此方法會被呼叫
mListView.setParallaxImage(mImage);
mHeaderView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
mListView.addHeaderView(mHeaderView);
mListView.setAdapter(new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1,Cheeses.NAMES));
}
}
view_header.xml
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/iv"
android:layout_width="match_parent"
android:layout_height="180dp"
android:scaleType="centerCrop"
android:src="@drawable/parallax" />
</LinearLayout>
Activity中的邏輯比較簡單,初始化下資料。
另外,注意在view_header.xml中設定圖片採用的拉伸方式是centerCrop,scaleType有很多屬性,這裡必須採用這種方式處理,採用其他的會使圖片拉伸的很不美觀,關於scaleType的多種屬性這裡就不詳細說明了,感興趣的童鞋可以自己研究下,後面如果有時間也會有這方面的筆記貼出來,O(∩_∩)O。
歡迎關注我的公眾號 ,不定期會有優質技術文章推送 。
微信掃一掃下方二維碼即可關注