背景
最近產(chǎn)品有個(gè)需求是給用戶(hù)上下滾動(dòng)播放運(yùn)營(yíng)消息昵济,這個(gè)需求我之前搞過(guò)铲觉,實(shí)現(xiàn)方式很是復(fù)雜逼蒙,采用的自定義view的方式繪制出來(lái)的从绘,今天用一種簡(jiǎn)單的方式實(shí)現(xiàn)
ViewFlipper
這個(gè)就是我們今天的主角,通過(guò)ViewFlipper就是可以實(shí)現(xiàn)View之間的切換
效果展示
效果展示可能有點(diǎn)卡頓是牢,但是實(shí)際上是不卡頓的
代碼示例
1.xml 描述View
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">
<ViewFlipper
android:id="@+id/viewFlipper"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.constraint.ConstraintLayout>
2.動(dòng)畫(huà)文件
anim_push_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromYDelta="100%p"
android:toYDelta="0"/>
<alpha
android:duration="500"
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
</set>
anim_push_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromYDelta="0"
android:toYDelta="-100%p"/>
<alpha
android:duration="500"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>
3.代碼編寫(xiě)
package demo.nate.com.viewflipper;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.widget.TextView;
import android.widget.ViewFlipper;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ViewFlipper mViewFlipper;
private String[] mContents = new String[]{"窗前明月光僵井。", "疑是地上霜","舉頭望明月","低頭思故鄉(xiāng)"};
private List<TextView> mViews = new ArrayList<>(mContents.length);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewFlipper = findViewById(R.id.viewFlipper);
createView();
addView();
mViewFlipper.setInAnimation(this, R.anim.anim_push_in);
mViewFlipper.setOutAnimation(this, R.anim.anim_push_out);
mViewFlipper.setFlipInterval(2000);
mViewFlipper.startFlipping();
}
private void addView() {
for (TextView view : mViews) {
mViewFlipper.addView(view);
}
}
private void createView() {
for (int i = 0; i < mContents.length; i++) {
TextView textView = new TextView(this);
textView.setTextSize(24);
textView.setGravity(Gravity.CENTER);
textView.setTextColor(getResources().getColor(R.color.colorAccent));
textView.setText(mContents[i]);
mViews.add(textView);
}
}
}
就這么簡(jiǎn)單
ViewFlipper 源碼分析
如果只是講使用的化,根本沒(méi)有必要驳棱,代碼太簡(jiǎn)單了批什,我希望能從原理上了解ViewFlipper,所以我需要分析一下他的源代碼
1.先分析一下繼承關(guān)系
其實(shí)ViewFlipper就是一個(gè)FrameLayout社搅,其核心代碼就是ViewAnimator上
2.ViewAnimtor 方法概率
上面這些方法比較關(guān)鍵驻债,我們后續(xù)詳細(xì)分析一下
總結(jié)主要是提供了一些動(dòng)畫(huà)設(shè)置接口
3.整個(gè)流程分析
啟動(dòng)動(dòng)畫(huà)
mViewFlipper.startFlipping();
/**
* Start a timer to cycle through child views
*/
public void startFlipping() {
mStarted = true;
updateRunning();
}
private void updateRunning() {
updateRunning(true);
}
mStarted 變量默認(rèn)是false,updateRunning()主要作用是循環(huán)執(zhí)行動(dòng)畫(huà)
private void updateRunning(boolean flipNow) {
boolean running = mVisible && mStarted && mUserPresent;
if (running != mRunning) {
if (running) {
showOnly(mWhichChild, flipNow);
postDelayed(mFlipRunnable, mFlipInterval);
} else {
removeCallbacks(mFlipRunnable);
}
mRunning = running;
}
if (LOGD) {
Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
+ ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
}
}
這個(gè)地方有幾個(gè)變量首先要識(shí)別一下形葬,mStarted 這個(gè)變量我們之前已經(jīng)看過(guò)了合呐,默認(rèn)值是false,啟動(dòng)動(dòng)畫(huà)的時(shí)候設(shè)置了true笙以,mVisible 這個(gè)變量我們先看一下這個(gè)值賦值是在哪里淌实?
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mVisible = false;
getContext().unregisterReceiver(mReceiver);
updateRunning();
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
mVisible = visibility == VISIBLE;
updateRunning(false);
}
其實(shí)就是在view顯示的時(shí)候,把這個(gè)值設(shè)置為true
現(xiàn)在只有mUserPresent的值我們還沒(méi)有分析了,通過(guò)分析代碼我們可以發(fā)現(xiàn)這個(gè)值在初始化的時(shí)候就是true
public class ViewFlipper extends ViewAnimator {
private static final String TAG = "ViewFlipper";
private static final boolean LOGD = false;
private static final int DEFAULT_INTERVAL = 3000;
private int mFlipInterval = DEFAULT_INTERVAL;
private boolean mAutoStart = false;
private boolean mRunning = false;
private boolean mStarted = false;
private boolean mVisible = false;
private boolean mUserPresent = true;
對(duì)他的修改也只是在接收屏幕關(guān)閉的廣播時(shí)候進(jìn)行修改
public ViewFlipper(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.ViewFlipper);
mFlipInterval = a.getInt(
com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL);
mAutoStart = a.getBoolean(
com.android.internal.R.styleable.ViewFlipper_autoStart, false);
a.recycle();
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
mUserPresent = false;
updateRunning();
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
mUserPresent = true;
updateRunning(false);
}
}
};
還是回到我們主線(xiàn)代碼中
private void updateRunning(boolean flipNow) {
boolean running = mVisible && mStarted && mUserPresent;
if (running != mRunning) {
if (running) {
showOnly(mWhichChild, flipNow);
postDelayed(mFlipRunnable, mFlipInterval);
} else {
removeCallbacks(mFlipRunnable);
}
mRunning = running;
}
if (LOGD) {
Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
+ ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
}
}
變量running的值是true拆祈,mRunning 的值是false恨闪,也就走到了showOnly這個(gè)方法中,然后postDelayed(mFlipRunnable, mFlipInterval)這個(gè)方法就是定時(shí)切換view然后執(zhí)行動(dòng)畫(huà)放坏,我們先看看showOnly方法
void showOnly(int childIndex, boolean animate) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (i == childIndex) {
if (animate && mInAnimation != null) {
child.startAnimation(mInAnimation);
}
child.setVisibility(View.VISIBLE);
mFirstTime = false;
} else {
if (animate && mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
child.startAnimation(mOutAnimation);
} else if (child.getAnimation() == mInAnimation)
child.clearAnimation();
child.setVisibility(View.GONE);
}
}
}
這個(gè)方法定義在ViewAnimator中凛剥,整體的邏輯也是比較簡(jiǎn)單的,就是根據(jù)childIndex的索引查找指定的view轻姿,執(zhí)行進(jìn)入動(dòng)畫(huà)犁珠,其他view執(zhí)行移出動(dòng)畫(huà),那么循環(huán)動(dòng)畫(huà)是如何執(zhí)行的呢互亮?
private final Runnable mFlipRunnable = new Runnable() {
@Override
public void run() {
if (mRunning) {
showNext();
postDelayed(mFlipRunnable, mFlipInterval);
}
}
};
public void showNext() {
setDisplayedChild(mWhichChild + 1);
}
public void setDisplayedChild(int whichChild) {
mWhichChild = whichChild;
if (whichChild >= getChildCount()) {
mWhichChild = 0;
} else if (whichChild < 0) {
mWhichChild = getChildCount() - 1;
}
boolean hasFocus = getFocusedChild() != null;
// This will clear old focus if we had it
showOnly(mWhichChild);
if (hasFocus) {
// Try to retake focus if we had it
requestFocus(FOCUS_FORWARD);
}
}
總體上就是控制childIndex的大小犁享,不讓其查過(guò)最大值和最小值,剩下邏輯跟之前一樣豹休,就是執(zhí)行動(dòng)畫(huà)
總結(jié)
- 從使用角度來(lái)看炊昆,ViewFlipper可以實(shí)現(xiàn)兩個(gè)View之間的任意動(dòng)畫(huà),讓開(kāi)發(fā)正更加關(guān)注動(dòng)畫(huà)的編寫(xiě)而不需要關(guān)系動(dòng)畫(huà)的執(zhí)行邏輯
- 從代碼設(shè)計(jì)角度來(lái)看威根,這種用FrameLayout來(lái)父布局凤巨,同過(guò)兩個(gè)view來(lái)不斷執(zhí)行動(dòng)畫(huà)來(lái)達(dá)到設(shè)計(jì)上的動(dòng)畫(huà)效果,這種實(shí)現(xiàn)方式很是簡(jiǎn)單洛搀,在工作中很有借鑒性