ViewFlipper源码分析

2019-03-11  本文已影响0人  饥饿的大灰狼

背景

最近产品有个需求是给用户上下滚动播放运营消息,这个需求我之前搞过,实现方式很是复杂,采用的自定义view的方式绘制出来的,今天用一种简单的方式实现

ViewFlipper

这个就是我们今天的主角,通过ViewFlipper就是可以实现View之间的切换

效果展示

ok.gif

效果展示可能有点卡顿,但是实际上是不卡顿的

代码示例

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.动画文件

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.代码编写

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[]{"窗前明月光。", "疑是地上霜","举头望明月","低头思故乡"};

    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);
        }
    }
}

就这么简单

ViewFlipper 源码分析

如果只是讲使用的化,根本没有必要,代码太简单了,我希望能从原理上了解ViewFlipper,所以我需要分析一下他的源代码

1.先分析一下继承关系

viewFlipper.png

其实ViewFlipper就是一个FrameLayout,其核心代码就是ViewAnimator上

2.ViewAnimtor 方法概率

ViewAnimator 方法上.png

上面这些方法比较关键,我们后续详细分析一下

ViewAnimator方法下.png

总结主要是提供了一些动画设置接口

3.整个流程分析

启动动画

        mViewFlipper.startFlipping();
 /**
     * Start a timer to cycle through child views
     */
    public void startFlipping() {
        mStarted = true;
        updateRunning();
    }
    
      private void updateRunning() {
        updateRunning(true);
    }
    

mStarted 变量默认是false,updateRunning()主要作用是循环执行动画

    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);
        }
    }

这个地方有几个变量首先要识别一下,mStarted 这个变量我们之前已经看过了,默认值是false,启动动画的时候设置了true,mVisible 这个变量我们先看一下这个值赋值是在哪里?

 @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);
    }

其实就是在view显示的时候,把这个值设置为true

现在只有mUserPresent的值我们还没有分析了,通过分析代码我们可以发现这个值在初始化的时候就是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;

对他的修改也只是在接收屏幕关闭的广播时候进行修改

  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);
            }
        }
    };

还是回到我们主线代码中

   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这个方法中,然后postDelayed(mFlipRunnable, mFlipInterval)这个方法就是定时切换view然后执行动画,我们先看看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);
            }
        }
    }

这个方法定义在ViewAnimator中,整体的逻辑也是比较简单的,就是根据childIndex的索引查找指定的view,执行进入动画,其他view执行移出动画,那么循环动画是如何执行的呢?

    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的大小,不让其查过最大值和最小值,剩下逻辑跟之前一样,就是执行动画

总结

  1. 从使用角度来看,ViewFlipper可以实现两个View之间的任意动画,让开发正更加关注动画的编写而不需要关系动画的执行逻辑
  2. 从代码设计角度来看,这种用FrameLayout来父布局,同过两个view来不断执行动画来达到设计上的动画效果,这种实现方式很是简单,在工作中很有借鉴性
上一篇下一篇

猜你喜欢

热点阅读