美文网首页
ViewFlipper源码分析

ViewFlipper源码分析

作者: 饥饿的大灰狼 | 来源:发表于2019-03-11 11:25 被阅读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来不断执行动画来达到设计上的动画效果,这种实现方式很是简单,在工作中很有借鉴性

    相关文章

      网友评论

          本文标题:ViewFlipper源码分析

          本文链接:https://www.haomeiwen.com/subject/ssuvpqtx.html