一,从源码上认识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"/>
从这两个动画,相信你更直观地看出怎么个垂直滚动法了。