欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-09-06 13:31:02
verticalbannerview是一个仿淘宝app首页轮播头条的自定义控件。 特性: 1.可*定义展示的内容。 2.使用方式类似listview/rec...

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

特性:

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

verticalbannerview开源项目地址

运行效果图:

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

一、项目使用

(1).添加项目依赖。

dependencies {
  compile 'com.github.rowandjj:verticalbannerview:1.0'
}

(2).添加布局。

<linearlayout
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:gravity="center_vertical"
  android:orientation="horizontal">
 
  <textview
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingleft="5dp"
    android:text="淘宝头条"
    android:textstyle="bold"/>
 
  <view
    android:layout_width="1dp"
    android:layout_height="40dp"
    android:layout_marginleft="5dp"
    android:layout_marginright="5dp"
    android:background="#cccccc"/>
 
  <com.taobao.library.verticalbannerview
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/banner"
    android:layout_width="wrap_content"
    android:layout_height="36dp"
    app:animduration="900"
    app:gap="2000"/>
</linearlayout>

(3).实现adapter。

public class sampleadapter extends basebanneradapter<model> {
  private list<model> mdatas;
 
  public sampleadapter01(list<model> datas) {
    super(datas);
  }
 
  @override
  public view getview(verticalbannerview parent) {
    return layoutinflater.from(parent.getcontext()).inflate(r.layout.your_item,null);
  }
 
  @override
  public void setitem(final view view, final model data) {
    textview textview = (textview) view.findviewbyid(r.id.text);
    textview.settext(data.title);
    // 你可以增加点击事件
    view.setonclicklistener(new view.onclicklistener() {
      @override
      public void onclick(view v) {
        // todo handle click event
      }
    });
  }
}

(4).为verticalbannerview设置adapter,并启动动画。

list<model> datas = new arraylist<>();
datas.add(new model01("note7发布了"));
datas.add(new model01("note7被召回了"));
sampleadapter adapter = new sampleadapter(datas);
verticalbannerview banner = (verticalbannerview) findviewbyid(r.id.banner);
banner.setadapter(adapter);
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绑定数据。

public abstract class basebanneradapter<t> {
  private list<t> mdatas;
  private ondatachangedlistener mondatachangedlistener;
 
  public basebanneradapter(list<t> datas) {
    mdatas = datas;
    if (datas == null || datas.isempty()) {
      throw new runtimeexception("nothing to show");
    }
  }
 
  public basebanneradapter(t[] datas) {
    mdatas = new arraylist<>(arrays.aslist(datas));
  }
 
  // 设置banner填充的数据
  public void setdata(list<t> datas) {
    this.mdatas = datas;
    notifydatachanged();
  }
 
  void setondatachangedlistener(ondatachangedlistener listener) {
    mondatachangedlistener = listener;
  }
 
  // 获取banner总数
  public int getcount() {
    return mdatas == null ? 0 : mdatas.size();
  }
 
  // 通知数据改变
  void notifydatachanged() {
    mondatachangedlistener.onchanged();
  }
 
  // 获取数据
  public t getitem(int position) {
    return mdatas.get(position);
  }
 
  // 设置banner的itemview
  public abstract view getview(verticalbannerview parent);
 
  // 设置banner的数据
  public abstract void setitem(view view, t data);
 
  // 数据变化的监听
  interface ondatachangedlistener {
    void onchanged();
  }
}

(2).verticalbannerview类

verticalbannerview类继承自linearlayout,并在构造方法中设定方向为垂直。同时verticalbannerview类实现了ondatachangedlistener接口,实现onchanged()方法,这样当改变数据后调用basebanneradapter的notifydatachanged()时,verticalbannerview的onchanged()方法被回调,执行setupadapter()重新初始化数据。

public class verticalbannerview extends linearlayout implements basebanneradapter.ondatachangedlistener {
  public verticalbannerview(context context, attributeset attrs, int defstyleattr) {
    super(context, attrs, defstyleattr);
    init(context, attrs, defstyleattr);
  }
 
  private void init(context context, attributeset attrs, int defstyleattr) {
    setorientation(vertical);
 ......
  }
 
  ......
 
  @override
  public void onchanged() {
    setupadapter();
  }
 
  ......
}

为verticalbannerview添加item数据,需要调用setadapter()方法,关键在于其中执行的setupadapter()方法。

public void setadapter(basebanneradapter adapter) {
  if (adapter == null) {
    throw new runtimeexception("adapter must not be null");
  }
  if (madapter != null) {
    throw new runtimeexception("you have already set an adapter");
  }
  this.madapter = adapter;
  madapter.setondatachangedlistener(this);
  setupadapter();
}

在setupadapter()方法中,先移除所有的子view,然后调用adapter的getview()方法创建两个子view,分别赋值给成员变量mfirstview和msecondview,并为这两个子view绑定数据,最后再调用addview()添加进来。

// 初始化child view
private void setupadapter() {
  // 先移除所有的子view
  removeallviews();
 
  if (madapter.getcount() == 1) {
    mfirstview = madapter.getview(this);
    madapter.setitem(mfirstview, madapter.getitem(0));
    addview(mfirstview);
  } else {
    // 调用adapter的getview()方法,创建两个子view,分别赋值给mfirstview和msecondview
    mfirstview = madapter.getview(this);
    msecondview = madapter.getview(this);
    // 使用索引0和1的数据,为mfirstview和msecondview设置数据
    madapter.setitem(mfirstview, madapter.getitem(0));
    madapter.setitem(msecondview, madapter.getitem(1));
    // 将mfirstview和msecondview添加到当前view
    addview(mfirstview);
    addview(msecondview);
 
    mposition = 1;
    isstarted = false;
  }
  setbackgrounddrawable(mfirstview.getbackground());
}

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

@override
protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
  super.onmeasure(widthmeasurespec, heightmeasurespec);
  // 成员变量mbannerheight有一个默认值
  if (layoutparams.wrap_content == getlayoutparams().height) {
    // 如果当前view的高度设置为wrap_content,使用默认值
    getlayoutparams().height = (int) mbannerheight;
  } else {
    // 如果当前view设定了固定高度,则使用设定的高度
    mbannerheight = getheight();
  }
  // 修改mfirstview和msecondview的高度为其父视图的高度
  if (mfirstview != null) {
    mfirstview.getlayoutparams().height = (int) mbannerheight;
  }
  if (msecondview != null) {
    msecondview.getlayoutparams().height = (int) mbannerheight;
  }
}

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

public void start() {
  if (madapter == null) {
    throw new runtimeexception("you must call setadapter() before start");
  }
 
  if (!isstarted && madapter.getcount() > 1) {
    isstarted = true;
    postdelayed(mrunnable, mgap);
  }
}
 
private animrunnable mrunnable = new animrunnable();
 
private class animrunnable implements runnable {
  @override
  public void run() {
    performswitch();
    // 调用postdelayed()延时再执行,一直循环下去
    postdelayed(this, mgap);
  }
}

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

// 执行切换
private void performswitch() {
  // 动画在执行过程中,view的translationy属性从0一直减小到-mbannerheight
  // 因为translationy属性一直是负值,所以view向上移动
  objectanimator animator1 = objectanimator.offloat(mfirstview, "translationy", -mbannerheight);
  objectanimator animator2 = objectanimator.offloat(msecondview, "translationy", -mbannerheight);
  animatorset set = new animatorset();
  set.playtogether(animator1, animator2);
  set.addlistener(new animatorlisteneradapter() {
    @override
    public void onanimationend(animator animation) {
      // 动画执行完成,把mfirstview和msecondview的translationy恢复到默认状态
      mfirstview.settranslationy(0);
      msecondview.settranslationy(0);
      // 使用下一条数据,设置到第一个子view
      view removedview = getchildat(0);
      mposition++;
      madapter.setitem(removedview, madapter.getitem(mposition % madapter.getcount()));
      // 移除第一个子view,此时当前linearlayout的childcount==1
      removeview(removedview);
      // 移除的view,再添加到第2个位置
      addview(removedview, 1);
    }
  });
  set.setduration(manimduration);
  set.start();
}

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

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