从源码角度上探索AdapterViewFlipper怎么实现广告栏的垂直自动滚动

一,从源码上认识AdapterViewFlipper
  我不知道大家有没有跟我一样,看到别人的app一些比较好的交互时,总是好奇别人是怎么实现的,如果是换做自己,我哦该怎么实现。最近在做一个横向轮播的视频展示需求,我们知道viewpager是可以很容易实现横向滚动的,那么很多app上的一些广告栏是垂直滚动的,比如京东,淘宝,一些招聘的app都有。那么别人是在怎么实现的呢。方式有很多中,如果只是简单的文本展示,那么TextView就完全可以搞定,这里有篇文章把TextView的用法都简单介绍 android高仿京东快报(垂直循环滚动新闻栏) 当然再复杂的都可以用自定义view来实现,但是写app的,我相信很多人跟我一样,不太喜欢写自定义控件。这时AdapterViewFlipper就闪亮登场了。我个人比较喜欢从源码上来研究一个控件的用法,下面是我自己的一些学习积累,希望对跟我之前一样不是很懂AdapterViewFlipper的人有一些帮助。
  那么AdapterViewFlipper到底是神马呢?官方是这么介绍它的:

/**
 * Simple {@link ViewAnimator} that will animate between two or more views
 * that have been added to it.  Only one child is shown at a time.  If
 * requested, can automatically flip between each child at a regular interval.
 *  * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
 * @attr ref android.R.styleable#AdapterViewFlipper_autoStart
 */

上面的英文都比较简单啦,大家都能看懂。其实AdapterViewFlipper继承了AdapterViewAnimator,它会显示一个View组件,可以通过showPrevious()和showNext()方法控制组件显示上一个、下一个组件。从上面可以看到AdapterViewFlipper有两个比较重要的属性。
* flipInterval :这个属性是动画持续的时间,也就是设置自动播放的时间间隔
* autoStart :这个属性从单词上都可以知道是啥意思,即设置显示该组件是否是自动播放

public class AdapterViewFlipper extends AdapterViewAnimator {
    private static final int DEFAULT_INTERVAL = 10000;
    private int mFlipInterval = DEFAULT_INTERVAL;
    private boolean mAutoStart = false;

    public AdapterViewFlipper(
            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        final TypedArray a = context.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.AdapterViewFlipper, defStyleAttr, defStyleRes);
        mFlipInterval = a.getInt(
                com.android.internal.R.styleable.AdapterViewFlipper_flipInterval, DEFAULT_INTERVAL);
        mAutoStart = a.getBoolean(
                com.android.internal.R.styleable.AdapterViewFlipper_autoStart, false);

        // A view flipper should cycle through the views
        mLoopViews = true;

        a.recycle();
    }

看上面的代码可以知道,flipInterval默认是10000,autoStart默认是false的,所以要想自动播放则需要把这个属性设为true。
那么AdapterViewFlipper是怎么来自动播放的呢,其实呢,View万变不离其中,View的“自动”一般都离不开View.java提供的这个方法:

 /**
     * 

Causes the Runnable to be added to the message queue, to be run * after the specified amount of time elapses. * The runnable will be run on the user interface thread.

* * @param action The Runnable that will be executed. * @param delayMillis The delay (in milliseconds) until the Runnable * will be executed. * * @return true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. Note that a * result of true does not mean the Runnable will be processed -- * if the looper is quit before the delivery time of the message * occurs then the message will be dropped. * * @see #post * @see #removeCallbacks */ public boolean postDelayed(Runnable action, long delayMillis) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.postDelayed(action, delayMillis); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().postDelayed(action, delayMillis); return true; }

postDelayed(Runnable action, long delayMillis)这个方法是什么鬼呢?其实这是Android 控件View自带的定时器 。这个定时器的用法,我们在开发中经常用的:例如 有一个需求是这样的,点击加关注按钮后,执行关注操作,成功后按钮文字变为“已关注”,保持3秒,三秒后按钮文字便问“取消关注”,点击后执行取消关注的操作。对于这样子的需求用postDelayed做是最方便的。

mButton.setText("已关注");
/*3秒内设置不可点击*/
mButton.setClickable(false);
mButton.postDelayed(new Runnable() {
    @Override
    public void run() {
        /*3秒后可以点击*/
     mButton.setClickable(true);
        mButton.setText("取消关注");
    }
},3*1000);

回到刚刚的话题,AdapterViewFlipper是怎么自动播放的,我跟踪到源码里面发现:

 @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        // Listen for broadcasts related to user-presence
        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_USER_PRESENT);

        // OK, this is gross but needed. This class is supported by the
        // remote views machanism and as a part of that the remote views
        // can be inflated by a context for another user without the app
        // having interact users permission - just for loading resources.
        // For exmaple, when adding widgets from a user profile to the
        // home screen. Therefore, we register the receiver as the current
        // user not the one the context is for.
        getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
                filter, null, getHandler());


        if (mAutoStart) {
            // Automatically start when requested
            startFlipping();
        }
    }

这个方法我相信对View有一定基础的人都知道是啥。我们先看看官方怎么介绍这个方法:

protected void onAttachedToWindow()
 This is called when the view is attached to a window. At this point it has a Surface and will start drawing. Note that this function is guaranteed to be called beforeonDraw(android.graphics.Canvas), however it may be called any time before the first onDraw -- including before or afteronMeasure(int, int). 

从开发文档中我们可以看出,onAttachedToWindow是在第一次onDraw前调用的。也就是我们写的View在没有绘制出来时调用的,但只会调用一次。比如,我们写状态栏中的时钟的View,在onAttachedToWindow这方法中做初始化工作,比如注册一些广播等等……也就是AdapterViewFlipper在onAttachedToWindow的时候会判断mAutoStart是否是true,如果是,则会走startFlipping()开启循环播放view

 /**
     * Start a timer to cycle through child views
     */
    public void startFlipping() {
        mStarted = true;
        updateRunning();
    }
     /**
     * Internal method to start or stop dispatching flip {@link Message} based
     * on {@link #mRunning} and {@link #mVisible} state.
     */
    private void updateRunning() {
        // by default when we update running, we want the
        // current view to animate in
        updateRunning(true);
    }
 /**
     * Internal method to start or stop dispatching flip {@link Message} based
     * on {@link #mRunning} and {@link #mVisible} state.
     *
     * @param flipNow Determines whether or not to execute the animation now, in
     *            addition to queuing future flips. If omitted, defaults to
     *            true.
     */
    private void updateRunning(boolean flipNow) {
        boolean running = !mAdvancedByHost && mVisible && mStarted && mUserPresent
                && mAdapter != null;
        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);
        }
    }

广播接收器:

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 final Runnable mFlipRunnable = new Runnable() {
        @Override
        public void run() {
            if (mRunning) {
                showNext();
            }
        }
    };

当然在摧毁view的时候就会停止自动播放:

  @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mVisible = false;

        getContext().unregisterReceiver(mReceiver);
        updateRunning();
    }

知道原理后,使用就简单多了,AdapterViewFlipper提供一个方法给我们设置自动播放功能:

 /**
     * Set if this view automatically calls {@link #startFlipping()} when it
     * becomes attached to a window.
     */
    public void setAutoStart(boolean autoStart) {
        mAutoStart = autoStart;
    }

我们只要调用这个方法就可以自动播放了。
二、怎么利用AdapterViewFlipper来进行垂直自动滚动广告栏
 下面让我用自己写的demo告诉你怎么做。
 1、先看布局文件
 


<RelativeLayout 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"
    tools:context="top.hellowoodes.adapterviewflipper.MainActivity">
    <AdapterViewFlipper
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/adapterViewFlipper"
        android:flipInterval="3000" //自动轮播时间间隔
        android:layout_centerInParent="true" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="自动播放"
        android:id="@+id/button3"
        android:onClick="auto"
        android:layout_alignParentBottom="true" />
RelativeLayout>

2、Java代码怎么写

AdapterViewFlipper肯定是可以让我们自定义适配器布局的,请看:
(1)先定义一个Adapter

  private  class MyAdapter extends BaseAdapter{
     int[] imageIds = new int[]{R.drawable.a2,R.drawable.a3,R.drawable.a4,R.drawable.a2,R.drawable.a3,R.drawable.a4};
        @Override
        public int getCount() {
            return imageIds.length;
        }

        @Override
        public Object getItem(int position) {
            return position;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView imageView = new ImageView(AdapterViewFlipperActivity.this); //这里你定义多复杂的布局文件都可以啦
            imageView.setImageResource(imageIds[position]);
            imageView.setScaleType(ImageView.ScaleType.FIT_XY);
            imageView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            return imageView;
        }
    }

(2)调用自动播放

/**
 * Created by wuchunmei on 6/27/17.
 */
public class AdapterViewFlipperActivity extends AppCompatActivity {
    AdapterViewFlipper adapterViewFlipper;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.flipper_layout);
        adapterViewFlipper = (AdapterViewFlipper) findViewById(R.id.adapterViewFlipper);
        MyAdapter adapter = new MyAdapter();
        adapterViewFlipper.setAdapter(adapter);
    }
    public void auto(View source) {
        //开始自动播放
        adapterViewFlipper.setInAnimation(this, R.animator.flipper_in);
        adapterViewFlipper.setOutAnimation(this, R.animator.flipper_out);
        adapterViewFlipper.startFlipping();
    }
}

在auto(View source)这个方法里面,你会看到setInAnimation跟setOutAnimation这两个方法,这是干什么用的呢。

 /**
     * Specifies the animation used to animate a View that enters the screen.
     *
     * @param context The application's environment.
     * @param resourceID The resource id of the animation.
     *
     * @see #getInAnimation()
     * @see #setInAnimation(android.animation.ObjectAnimator)
     */
    public void setInAnimation(Context context, int resourceID) {
        setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
    }

这个是控制view进入屏幕的动画

 /**
     * Specifies the animation used to animate a View that exit the screen.
     *
     * @param outAnimation The animation started when a View exit the screen.
     *
     * @see #getOutAnimation()
     * @see #setOutAnimation(android.content.Context, int)
     */
    public void setOutAnimation(ObjectAnimator outAnimation) {
        mOutAnimation = outAnimation;
    }

这个是view退出屏幕的动画。
所以到这里你懂了么,其实这两个进出的动画就是控制view是垂直滚动的还是横向滚动的啦。
但是这里需要特别注意:AdapterViewFlipper这个控件,setInAnimation跟setOutAnimation只能出入属性动画,出入tween动画无效,亲身试过了
看我写的动画,在res下新建一个animator的文件夹:
(1)进入动画:



<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:propertyName="y"
    android:valueFrom="1000"
    android:valueTo="0"
    android:valueType="floatType"/>

(2)退出动画


<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:propertyName="y"
    android:valueFrom="0"
    android:valueTo="-1000"
    android:valueType="floatType"/>

从这两个动画,相信你更直观地看出怎么个垂直滚动法了。

你可能感兴趣的:(Android)