热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

这篇文章主要为大家详细介绍了VerticalBannerView仿淘宝头条实现垂直轮播广告,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

特性:

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

VerticalBannerView开源项目地址

运行效果图:


一、项目使用

(1).添加项目依赖。

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

(2).添加布局。


 
  
 
  
 
  

(3).实现Adapter。

public class SampleAdapter extends BaseBannerAdapter {
  private List mDatas;
 
  public SampleAdapter01(List 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 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 {
  private List mDatas;
  private OnDataChangedListener mOnDataChangedListener;
 
  public BaseBannerAdapter(List 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 datas) {
    this.mDatas = datas;
    notifyDataChanged();
  }
 
  void setOnDataChangedListener(OnDataChangedListener listener) {
    mOnDataChangedListener= listener;
  }
 
  // 获取banner总数
  public int getCount() {
    return mDatas == null &#63; 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就这样一直循环切换。

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


推荐阅读
  • 在Notepad++中配置Markdown语法高亮及实时预览功能
    本文详细介绍了如何在Notepad++中配置Markdown语法高亮和实时预览功能,包括必要的插件安装和设置步骤。 ... [详细]
  • 处理Android EditText中数字输入与parseInt方法
    本文探讨了如何在Android应用中从EditText组件安全地获取并解析用户输入的数字,特别是用于设置端口号的情况。通过示例代码和异常处理策略,展示了有效的方法来避免因非法输入导致的应用崩溃。 ... [详细]
  • 默认情况下,Git 使用 Nano 编辑器进行提交信息的编辑,但如果您更喜欢使用 Vim,可以通过简单的配置更改来实现这一变化。本文将指导您如何通过修改全局配置文件来设置 Vim 作为默认的 Git 提交编辑器。 ... [详细]
  • Android 中的布局方式之线性布局
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 在1995年,Simon Plouffe 发现了一种特殊的求和方法来表示某些常数。两年后,Bailey 和 Borwein 在他们的论文中发表了这一发现,这种方法被命名为 Bailey-Borwein-Plouffe (BBP) 公式。该问题要求计算圆周率 π 的第 n 个十六进制数字。 ... [详细]
  • 2023年,Android开发前景如何?25岁还能转行吗?
    近期,关于Android开发行业的讨论在多个平台上热度不减,许多人担忧其未来发展。本文将探讨当前Android开发市场的现状、薪资水平及职业选择建议。 ... [详细]
  • 使用TabActivity实现Android顶部选项卡功能
    本文介绍如何通过继承TabActivity来创建Android应用中的顶部选项卡。通过简单的步骤,您可以轻松地添加多个选项卡,并实现基本的界面切换功能。 ... [详细]
  • Android与JUnit集成测试实践
    本文探讨了如何在Android项目中集成JUnit进行单元测试,并详细介绍了修改AndroidManifest.xml文件以支持测试的方法。 ... [详细]
  • 本文介绍了SIP(Session Initiation Protocol,会话发起协议)的基本概念、功能、消息格式及其实现机制。SIP是一种在IP网络上用于建立、管理和终止多媒体通信会话的应用层协议。 ... [详细]
  • 二维码的实现与应用
    本文介绍了二维码的基本概念、分类及其优缺点,并详细描述了如何使用Java编程语言结合第三方库(如ZXing和qrcode.jar)来实现二维码的生成与解析。 ... [详细]
  • 在日常生活中,支付宝已成为不可或缺的支付工具之一。本文将详细介绍如何通过支付宝实现免费提现,帮助用户更好地管理个人财务,避免不必要的手续费支出。 ... [详细]
  • JUnit下的测试和suite
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 我的读书清单(持续更新)201705311.《一千零一夜》2006(四五年级)2.《中华上下五千年》2008(初一)3.《鲁滨孙漂流记》2008(初二)4.《钢铁是怎样炼成的》20 ... [详细]
  • 本文介绍了如何通过C#语言调用动态链接库(DLL)中的函数来实现IC卡的基本操作,包括初始化设备、设置密码模式、获取设备状态等,并详细展示了将TextBox中的数据写入IC卡的具体实现方法。 ... [详细]
  • 本文探讨了如何通过Service Locator模式来简化和优化在B/S架构中的服务命名访问,特别是对于需要频繁访问的服务,如JNDI和XMLNS。该模式通过缓存机制减少了重复查找的成本,并提供了对多种服务的统一访问接口。 ... [详细]
author-avatar
兆龙77
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有