当前位置: 移动技术网 > IT编程>移动开发>Android > VerticalBannerView仿淘宝头条实现垂直轮播广告

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

2019年09月09日  | 移动技术网IT编程  | 我要评论

台湾雅虎奇摩,天地无伦 快播,河源在线

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

特性:

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

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就这样一直循环切换。

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网