VerticalBannerView仿淘寶頭條實現(xiàn)垂直輪播廣告
VerticalBannerView是一個仿淘寶APP首頁輪播頭條的自定義控件。
特性:
1.可自由定義展示的內(nèi)容。
2.使用方式類似ListView/RecyclerView。
3.可為當前顯示的內(nèi)容添加各種事件,比如點擊打開某個頁面等。
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).實現(xiàn)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設(shè)置Adapter,并啟動動畫。
List<Model> datas = new ArrayList<>();
datas.add(new Model01("Note7發(fā)布了"));
datas.add(new Model01("Note7被召回了"));
SampleAdapter adapter = new SampleAdapter(datas);
VerticalBannerView banner = (VerticalBannerView) findViewById(R.id.banner);
banner.setAdapter(adapter);
banner.start();
二、源碼分析
實現(xiàn)原理:
VerticalBannerView本質(zhì)上是一個垂直的LinearLayout。定義一個Adapter類,向LinearLayout提供子View。初始狀態(tài)下往LinearLayout中添加兩個子View,子View的高度同LinearLayout的高度一致,這樣一來只有第1個子View顯示出來,第2個子View在底部不顯示。然后使用屬性動畫ObjectAnimator同時修改兩個子View的translationY屬性,動畫執(zhí)行過程中translationY從默認值0漸變到負的LinearLayout的高度,顯示出來的效果就是第1個子View逐漸向上退出,第2個子View從底部向上逐漸顯示。動畫執(zhí)行完畢后,移除第1個子View,這樣第2個子View的索引變成0,并完全顯示出來占據(jù)LinearLayout的高度。再將已經(jīng)移除的第1個子View,添加到索引為1的位置,此時該子View超出父視圖之外完全不顯示。一輪動畫執(zhí)行完畢,再調(diào)用postDelay()方法重復(fù)上述動畫,一直循環(huán)下去。
下面進入代碼部分,主要是兩個類BaseBannerAdapter和VerticalBannerView。
(1).BaseBannerAdapter類
BaseBannerAdapter類負責為廣告欄提供數(shù)據(jù)。我們在使用時,需要寫一個Adapter類繼承BaseBannerAdapter,實現(xiàn)getView()和setItem()方法。在getView()方法中,我們需要把要添加到廣告欄中的item view創(chuàng)建出來并返回,setItem()方法則負責為創(chuàng)建的item view綁定數(shù)據(jù)。
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));
}
// 設(shè)置banner填充的數(shù)據(jù)
public void setData(List<T> datas) {
this.mDatas = datas;
notifyDataChanged();
}
void setOnDataChangedListener(OnDataChangedListener listener) {
mOnDataChangedListener = listener;
}
// 獲取banner總數(shù)
public int getCount() {
return mDatas == null ? 0 : mDatas.size();
}
// 通知數(shù)據(jù)改變
void notifyDataChanged() {
mOnDataChangedListener.onChanged();
}
// 獲取數(shù)據(jù)
public T getItem(int position) {
return mDatas.get(position);
}
// 設(shè)置banner的ItemView
public abstract View getView(VerticalBannerView parent);
// 設(shè)置banner的數(shù)據(jù)
public abstract void setItem(View view, T data);
// 數(shù)據(jù)變化的監(jiān)聽
interface OnDataChangedListener {
void onChanged();
}
}
(2).VerticalBannerView類
VerticalBannerView類繼承自LinearLayout,并在構(gòu)造方法中設(shè)定方向為垂直。同時VerticalBannerView類實現(xiàn)了OnDataChangedListener接口,實現(xiàn)onChanged()方法,這樣當改變數(shù)據(jù)后調(diào)用BaseBannerAdapter的notifyDataChanged()時,VerticalBannerView的onChanged()方法被回調(diào),執(zhí)行setupAdapter()重新初始化數(shù)據(jù)。
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數(shù)據(jù),需要調(diào)用setAdapter()方法,關(guān)鍵在于其中執(zhí)行的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,然后調(diào)用Adapter的getView()方法創(chuàng)建兩個子View,分別賦值給成員變量mFirstView和mSecondView,并為這兩個子View綁定數(shù)據(jù),最后再調(diào)用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 {
// 調(diào)用Adapter的getView()方法,創(chuàng)建兩個子View,分別賦值給mFirstView和mSecondView
mFirstView = mAdapter.getView(this);
mSecondView = mAdapter.getView(this);
// 使用索引0和1的數(shù)據(jù),為mFirstView和mSecondView設(shè)置數(shù)據(jù)
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());
}
為了實現(xiàn)子View之間的切換,需要把上面添加進來的子View的高度修改為與VerticalBannerView高度一致。這個操作在onMeasure()方法中完成。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 成員變量mBannerHeight有一個默認值
if (LayoutParams.WRAP_CONTENT == getLayoutParams().height) {
// 如果當前View的高度設(shè)置為wrap_content,使用默認值
getLayoutParams().height = (int) mBannerHeight;
} else {
// 如果當前View設(shè)定了固定高度,則使用設(shè)定的高度
mBannerHeight = getHeight();
}
// 修改mFirstView和mSecondView的高度為其父視圖的高度
if (mFirstView != null) {
mFirstView.getLayoutParams().height = (int) mBannerHeight;
}
if (mSecondView != null) {
mSecondView.getLayoutParams().height = (int) mBannerHeight;
}
}
上面的準備工作完成后,就可以進入動畫的執(zhí)行了。VerticalBannerView通過調(diào)用start()方法啟動切換動畫。在start()方法中,調(diào)用postDelayed()執(zhí)行AnimRunnable任務(wù),在AnimRunnable的run()方法中,先調(diào)用performSwitch(),然后再次調(diào)用postDelayed()使AnimRunnable任務(wù)一直循環(huán)執(zhí)行下去。兩個子View之間的切換工作由performSwitch()負責執(zhí)行。
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();
// 調(diào)用postDelayed()延時再執(zhí)行,一直循環(huán)下去
postDelayed(this, mGap);
}
}
下面再進入performSwitch()方法,該方法是Item View之間產(chǎn)生切換效果的核心。
// 執(zhí)行切換
private void performSwitch() {
// 動畫在執(zhí)行過程中,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) {
// 動畫執(zhí)行完成,把mFirstView和mSecondView的translationY恢復(fù)到默認狀態(tài)
mFirstView.setTranslationY(0);
mSecondView.setTranslationY(0);
// 使用下一條數(shù)據(jù),設(shè)置到第一個子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屬性,動畫執(zhí)行中translationY從默認值0一直減小到-mBannerHeight,在這個過程中mFirstView逐漸向上退出,mSecondView從底部逐漸顯現(xiàn)。動畫執(zhí)行完畢后,移除mFirstView,mSecondView變成第1個子View并完全顯示出來填充父視圖的高度。再將移除的mFirstView添加到第2個位置,此時mFirstView未顯示出來。由于performSwitch()方法一直循環(huán)被調(diào)用,mFirstView和mSecondView就這樣一直循環(huán)切換。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android編程實現(xiàn)自定義PopupMenu樣式示例【顯示圖標與設(shè)置RadioButton圖標】
這篇文章主要介紹了Android編程實現(xiàn)自定義PopupMenu樣式功能,結(jié)合實例形式分析了Android顯示圖標與設(shè)置RadioButton圖標相關(guān)操作技巧,需要的朋友可以參考下2017-01-01
Android程序開發(fā)之UIScrollerView里有兩個tableView
這篇文章主要介紹了UIScrollerView里有兩個tableView 的相關(guān)資料,需要的朋友可以參考下2016-04-04
Android BLE設(shè)置MTU大小實現(xiàn)詳解
這篇文章主要為大家介紹了Android BLE設(shè)置MTU大小實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04
Android使用post方式上傳圖片到服務(wù)器的方法
這篇文章主要介紹了Android使用post方式上傳圖片到服務(wù)器的方法,結(jié)合實例形式分析了Android文件傳輸?shù)南嚓P(guān)技巧,需要的朋友可以參考下2016-03-03
基于Android week view仿小米和iphone日歷效果
這篇文章主要為大家詳細介紹了基于Android week view仿小米和iphone日歷效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11
Android實現(xiàn)EventBus登錄界面與傳值(粘性事件)
這篇文章主要為大家詳細介紹了Android實現(xiàn)EventBus登錄界面與傳值,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11

