VerticalBannerView仿淘宝头条实现垂直轮播广告
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就这样一直循环切换。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。