VerticalBannerView仿淘宝头条实现垂直轮播广告

前端之家收集整理的这篇文章主要介绍了VerticalBannerView仿淘宝头条实现垂直轮播广告前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

VerticalBannerView是一个仿淘宝APP首页轮播头条的自定义控件。

特性:

1.可自由定义展示的内容
2.使用方式类似ListView/RecyclerView。
3.可为当前显示内容添加各种事件,比如点击打开某个页面等。

VerticalBannerView开源项目地址

运行效果图:

VerticalBannerView仿淘宝头条实现垂直轮播广告

一、项目使用

(1).添加项目依赖。

  1. dependencies {
  2. compile 'com.github.Rowandjj:VerticalBannerView:1.0'
  3. }

(2).添加布局。

  1. <LinearLayout
  2. android:layout_width="match_parent"
  3. android:layout_height="wrap_content"
  4. android:gravity="center_vertical"
  5. android:orientation="horizontal">
  6.  
  7. <TextView
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:paddingLeft="5dp"
  11. android:text="淘宝头条"
  12. android:textStyle="bold"/>
  13.  
  14. <View
  15. android:layout_width="1dp"
  16. android:layout_height="40dp"
  17. android:layout_marginLeft="5dp"
  18. android:layout_marginRight="5dp"
  19. android:background="#cccccc"/>
  20.  
  21. <com.taobao.library.VerticalBannerView
  22. xmlns:app="http://schemas.android.com/apk/res-auto"
  23. android:id="@+id/banner"
  24. android:layout_width="wrap_content"
  25. android:layout_height="36dp"
  26. app:animDuration="900"
  27. app:gap="2000"/>
  28. </LinearLayout>

(3).实现Adapter。

  1. public class SampleAdapter extends BaseBannerAdapter<Model> {
  2. private List<Model> mDatas;
  3.  
  4. public SampleAdapter01(List<Model> datas) {
  5. super(datas);
  6. }
  7.  
  8. @Override
  9. public View getView(VerticalBannerView parent) {
  10. return LayoutInflater.from(parent.getContext()).inflate(R.layout.your_item,null);
  11. }
  12.  
  13. @Override
  14. public void setItem(final View view,final Model data) {
  15. TextView textView = (TextView) view.findViewById(R.id.text);
  16. textView.setText(data.title);
  17. // 你可以增加点击事件
  18. view.setOnClickListener(new View.OnClickListener() {
  19. @Override
  20. public void onClick(View v) {
  21. // TODO handle click event
  22. }
  23. });
  24. }
  25. }

(4).为VerticalBannerView设置Adapter,并启动动画。

  1. List<Model> datas = new ArrayList<>();
  2. datas.add(new Model01("Note7发布了"));
  3. datas.add(new Model01("Note7被召回了"));
  4. SampleAdapter adapter = new SampleAdapter(datas);
  5. VerticalBannerView banner = (VerticalBannerView) findViewById(R.id.banner);
  6. banner.setAdapter(adapter);
  7. banner.start();

二、源码分析

实现原理:

VerticalBannerView本质上是一个垂直的LinearLayout。定义一个Adapter类,向LinearLayout提供子View。初始状态下往LinearLayout中添加两个子View,子View的高度同LinearLayout的高度一致,这样一来只有第1个子View显示出来,第2个子View在底部不显示。然后使用属性动画ObjectAnimator同时修改两个子View的translationY属性,动画执行过程中translationY从默认值0渐变到负的LinearLayout的高度,显示出来的效果就是第1个子View逐渐向上退出,第2个子View从底部向上逐渐显示。动画执行完毕后,移除第1个子View,这样第2个子View的索引变成0,并完全显示出来占据LinearLayout的高度。再将已经移除的第1个子View,添加到索引为1的位置,此时该子View超出父视图之外完全不显示。一轮动画执行完毕,再调用postDelay()方法重复上述动画,一直循环下去。

下面进入代码部分,主要是两个类BaseBannerAdapter和VerticalBannerView。

(1).BaseBannerAdapter类

BaseBannerAdapter类负责为广告栏提供数据。我们在使用时,需要写一个Adapter类继承BaseBannerAdapter,实现getView()和setItem()方法。在getView()方法中,我们需要把要添加到广告栏中的item view创建出来并返回,setItem()方法则负责为创建的item view绑定数据。

  1. public abstract class BaseBannerAdapter<T> {
  2. private List<T> mDatas;
  3. private OnDataChangedListener mOnDataChangedListener;
  4.  
  5. public BaseBannerAdapter(List<T> datas) {
  6. mDatas = datas;
  7. if (datas == null || datas.isEmpty()) {
  8. throw new RuntimeException("nothing to show");
  9. }
  10. }
  11.  
  12. public BaseBannerAdapter(T[] datas) {
  13. mDatas = new ArrayList<>(Arrays.asList(datas));
  14. }
  15.  
  16. // 设置banner填充的数据
  17. public void setData(List<T> datas) {
  18. this.mDatas = datas;
  19. notifyDataChanged();
  20. }
  21.  
  22. void setOnDataChangedListener(OnDataChangedListener listener) {
  23. mOnDataChangedListener = listener;
  24. }
  25.  
  26. // 获取banner总数
  27. public int getCount() {
  28. return mDatas == null ? 0 : mDatas.size();
  29. }
  30.  
  31. // 通知数据改变
  32. void notifyDataChanged() {
  33. mOnDataChangedListener.onChanged();
  34. }
  35.  
  36. // 获取数据
  37. public T getItem(int position) {
  38. return mDatas.get(position);
  39. }
  40.  
  41. // 设置banner的ItemView
  42. public abstract View getView(VerticalBannerView parent);
  43.  
  44. // 设置banner的数据
  45. public abstract void setItem(View view,T data);
  46.  
  47. // 数据变化的监听
  48. interface OnDataChangedListener {
  49. void onChanged();
  50. }
  51. }

(2).VerticalBannerView类

VerticalBannerView类继承自LinearLayout,并在构造方法中设定方向为垂直。同时VerticalBannerView类实现了OnDataChangedListener接口,实现onChanged()方法,这样当改变数据后调用BaseBannerAdapter的notifyDataChanged()时,VerticalBannerView的onChanged()方法被回调,执行setupAdapter()重新初始化数据。

  1. public class VerticalBannerView extends LinearLayout implements BaseBannerAdapter.OnDataChangedListener {
  2. public VerticalBannerView(Context context,AttributeSet attrs,int defStyleAttr) {
  3. super(context,attrs,defStyleAttr);
  4. init(context,defStyleAttr);
  5. }
  6.  
  7. private void init(Context context,int defStyleAttr) {
  8. setOrientation(VERTICAL);
  9. ......
  10. }
  11.  
  12. ......
  13.  
  14. @Override
  15. public void onChanged() {
  16. setupAdapter();
  17. }
  18.  
  19. ......
  20. }

为VerticalBannerView添加item数据,需要调用setAdapter()方法,关键在于其中执行的setupAdapter()方法

  1. public void setAdapter(BaseBannerAdapter adapter) {
  2. if (adapter == null) {
  3. throw new RuntimeException("adapter must not be null");
  4. }
  5. if (mAdapter != null) {
  6. throw new RuntimeException("you have already set an Adapter");
  7. }
  8. this.mAdapter = adapter;
  9. mAdapter.setOnDataChangedListener(this);
  10. setupAdapter();
  11. }

在setupAdapter()方法中,先移除所有的子View,然后调用Adapter的getView()方法创建两个子View,分别赋值给成员变量mFirstView和mSecondView,并为这两个子View绑定数据,最后再调用addView()添加进来。

  1. // 初始化Child View
  2. private void setupAdapter() {
  3. // 先移除所有的子View
  4. removeAllViews();
  5.  
  6. if (mAdapter.getCount() == 1) {
  7. mFirstView = mAdapter.getView(this);
  8. mAdapter.setItem(mFirstView,mAdapter.getItem(0));
  9. addView(mFirstView);
  10. } else {
  11. // 调用Adapter的getView()方法,创建两个子View,分别赋值给mFirstView和mSecondView
  12. mFirstView = mAdapter.getView(this);
  13. mSecondView = mAdapter.getView(this);
  14. // 使用索引0和1的数据,为mFirstView和mSecondView设置数据
  15. mAdapter.setItem(mFirstView,mAdapter.getItem(0));
  16. mAdapter.setItem(mSecondView,mAdapter.getItem(1));
  17. // 将mFirstView和mSecondView添加到当前View
  18. addView(mFirstView);
  19. addView(mSecondView);
  20.  
  21. mPosition = 1;
  22. isStarted = false;
  23. }
  24. setBackgroundDrawable(mFirstView.getBackground());
  25. }

为了实现子View之间的切换,需要把上面添加进来的子View的高度修改为与VerticalBannerView高度一致。这个操作在onMeasure()方法中完成。

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
  3. super.onMeasure(widthMeasureSpec,heightMeasureSpec);
  4. // 成员变量mBannerHeight有一个默认值
  5. if (LayoutParams.WRAP_CONTENT == getLayoutParams().height) {
  6. // 如果当前View的高度设置为wrap_content,使用默认值
  7. getLayoutParams().height = (int) mBannerHeight;
  8. } else {
  9. // 如果当前View设定了固定高度,则使用设定的高度
  10. mBannerHeight = getHeight();
  11. }
  12. // 修改mFirstView和mSecondView的高度为其父视图的高度
  13. if (mFirstView != null) {
  14. mFirstView.getLayoutParams().height = (int) mBannerHeight;
  15. }
  16. if (mSecondView != null) {
  17. mSecondView.getLayoutParams().height = (int) mBannerHeight;
  18. }
  19. }

上面的准备工作完成后,就可以进入动画的执行了。VerticalBannerView通过调用start()方法启动切换动画。在start()方法中,调用postDelayed()执行AnimRunnable任务,在AnimRunnable的run()方法中,先调用performSwitch(),然后再次调用postDelayed()使AnimRunnable任务一直循环执行下去。两个子View之间的切换工作由performSwitch()负责执行。

  1. public void start() {
  2. if (mAdapter == null) {
  3. throw new RuntimeException("you must call setAdapter() before start");
  4. }
  5.  
  6. if (!isStarted && mAdapter.getCount() > 1) {
  7. isStarted = true;
  8. postDelayed(mRunnable,mGap);
  9. }
  10. }
  11.  
  12. private AnimRunnable mRunnable = new AnimRunnable();
  13.  
  14. private class AnimRunnable implements Runnable {
  15. @Override
  16. public void run() {
  17. performSwitch();
  18. // 调用postDelayed()延时再执行,一直循环下去
  19. postDelayed(this,mGap);
  20. }
  21. }

下面再进入performSwitch()方法,该方法是Item View之间产生切换效果的核心。

  1. // 执行切换
  2. private void performSwitch() {
  3. // 动画在执行过程中,View的translationY属性从0一直减小到-mBannerHeight
  4. // 因为translationY属性一直是负值,所以View向上移动
  5. ObjectAnimator animator1 = ObjectAnimator.ofFloat(mFirstView,"translationY",-mBannerHeight);
  6. ObjectAnimator animator2 = ObjectAnimator.ofFloat(mSecondView,-mBannerHeight);
  7. AnimatorSet set = new AnimatorSet();
  8. set.playTogether(animator1,animator2);
  9. set.addListener(new AnimatorListenerAdapter() {
  10. @Override
  11. public void onAnimationEnd(Animator animation) {
  12. // 动画执行完成,把mFirstView和mSecondView的translationY恢复到默认状态
  13. mFirstView.setTranslationY(0);
  14. mSecondView.setTranslationY(0);
  15. // 使用下一条数据,设置到第一个子View
  16. View removedView = getChildAt(0);
  17. mPosition++;
  18. mAdapter.setItem(removedView,mAdapter.getItem(mPosition % mAdapter.getCount()));
  19. // 移除第一个子View,此时当前LinearLayout的childCount==1
  20. removeView(removedView);
  21. // 移除的View,再添加到第2个位置
  22. addView(removedView,1);
  23. }
  24. });
  25. set.setDuration(mAnimDuration);
  26. set.start();
  27. }

在performSwitch()方法中,使用属性动画ObjectAnimator同时修改两个mFirstView和mSecondView的translationY属性,动画执行中translationY从默认值0一直减小到-mBannerHeight,在这个过程中mFirstView逐渐向上退出,mSecondView从底部逐渐显现。动画执行完毕后,移除mFirstView,mSecondView变成第1个子View并完全显示出来填充父视图的高度。再将移除的mFirstView添加到第2个位置,此时mFirstView未显示出来。由于performSwitch()方法一直循环被调用,mFirstView和mSecondView就这样一直循环切换。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

猜你在找的Android相关文章