自定義Banner廣告條
我們在APP上都見過廣告條,幾張圖片輪播,今天,我們就自定義一個廣告條View,先來看看效果(gif轉出來效果可能不太好)
首先,我們先說一下,這個自定義View(起名爲BannerView)是由哪些控件組成的。
顯示圖片並且一直輪播的使用的是ViewPager,照片上一個灰色的半透明是一個LinearLayout,裏面包含TextView顯示文字,還有一個LinearLayout,負責顯示原點,表示當前是第幾張圖片。
看我們的佈局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--負責顯示圖片-->
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewpager_main"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!--顯示文字和圓點-->
<LinearLayout
android:id="@+id/ll_status_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/viewpager_main"
android:background="#44000000"
android:orientation="vertical"
android:visibility="gone">
<!--文字-->
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="2dp"
android:textColor="#ffffff" />
<!--圓點-->
<LinearLayout
android:id="@+id/ll_point_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="5dp" />
</LinearLayout>
</RelativeLayout>
佈局文件已經寫好,下一步就開始擼代碼啦
新建一個class文件FxBannerView(名字隨便取),繼承RelativeLayout,因爲我們佈局文件的根佈局使用的是RelativeLayout,然後重寫三個構造方法,這是每一個自定義控件都必須要做的
public FxBannerView(Context context) {
this(context, null);
mContext = context;
}
public FxBannerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
mContext = context;
}
public FxBannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
}
接下來,我們要完成PageView的無限滾動,圓點的創建及顏色變化,還有文字隨圖片變化而變化
- 首先,我們需要設置數據來源,既然是一張圖片配一行文字,那就使用LinkedHashMap<int,String> mData,然後解析出圖片集合imgs和文字集合strings
private void initData() {
imgs.clear();
strings.clear();
for (Object o : mData.entrySet()) {
Map.Entry entry = (Map.Entry) o;
imgs.add((int) entry.getKey());
strings.add((String) entry.getValue());
}
}
- 然後,我們獲取佈局文件的控件
private void initView(Context context) {
View root = LayoutInflater.from(context).inflate(R.layout.banner_view, this);
mLlPointGroup = root.findViewById(R.id.ll_point_group);//圓點集合
mTvName = root.findViewById(R.id.tv_name);//文字顯示
mViewpagerMain = root.findViewById(R.id.viewpager_main);//viewpager顯示圖片輪播
...
}
- 將數據傳給控件
for (int i = 0; i < imgs.size(); i++) {
int imgSrc = imgs.get(i);
// 添加圖
ImageView imageView = new ImageView(context);
imageView.setBackgroundResource(imgSrc);
imageViews.add(imageView);
// 添加點
ImageView pointView = new ImageView(context);
pointView.setBackgroundResource(R.drawable.point_seletor);
// px轉dp
float scale = getResources().getDisplayMetrics().density;
int width = (int) (scale * 8 + 0.5f);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, width);
params.rightMargin = width;
pointView.setLayoutParams(params);
mLlPointGroup.addView(pointView);
if (i == 0) {
pointView.setEnabled(true);
} else {
pointView.setEnabled(false);
}
}
繪製點使用的背景資源使用select和shape,使用shape畫出兩個圓,一個灰色一個紅色,表示選中和非選中,然後select根據enable屬性決定他是紅色還是灰色
point_seletor.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/point_disable" android:state_enabled="false" />
<item android:drawable="@drawable/point_enable" android:state_enabled="true" />
</selector>
point_disable.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="8dp"
android:height="8dp" />
<solid android:color="#44000000" />
</shape>
point_enable.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="8dp"
android:height="8dp" />
<solid android:color="#FF0000" />
</shape>
- 我們已經拿到了包含ImageView集合了,下一步就是將集合傳給ViewPager,使用過ViewPager的小夥伴一定知道ViewPager需要和PagerAdapter一起使用,自定義PagerAdapter在我另一篇博客中有說明
- 我們將imageView集合傳給adapter
myAdapter.setImageViews(imageViews);
mViewpagerMain.setAdapter(myAdapter);
myAdapter.notifyDataSetChanged();
- pageView數據設置完後,我們設置TextView,
mTvName.setText(strings.get(prePosition));
我們的自定義BannerView就完成了
完整代碼
package com.felix.baselibrary.UI.banner;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.viewpager.widget.ViewPager;
import com.felix.baselibrary.R;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 廣告條
* 必須使用setData方法將數據傳入,否則不能顯示
*/
public class FxBannerView extends RelativeLayout {
// TODO: 2020/4/20 數據來源可配置
private ViewPager mViewpagerMain;
private TextView mTvName;
private LinearLayout mLlPointGroup;
private LinkedHashMap<Integer, String> mData = new LinkedHashMap<>();
private ArrayList<Integer> imgs = new ArrayList<>();
private ArrayList<String> strings = new ArrayList<>();
private ArrayList<ImageView> imageViews;
private final static String TAG = FxBannerView.class.getSimpleName();
private int prePosition = 0;
private boolean isCycle = true;
private long mDelayMillis = 4000;
private FxBannerAdapter myAdapter;
private Context mContext;
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
mViewpagerMain.setCurrentItem(mViewpagerMain.getCurrentItem() + 1);
mHandler.sendEmptyMessageDelayed(0, mDelayMillis);
return true;
}
});
private LinearLayout mLlStatusBar;
private onBannerItemClickListener mItemClickListener;
/**
* @param adapter BannerAdapter
* @param map KV結構map,HashMap<Integer, String>
* Integer:圖片的標識,R.drawable.xxx
* String:圖片描述
*/
public void setAdapter(FxBannerAdapter adapter, LinkedHashMap<Integer, String> map) {
myAdapter = new FxBannerAdapter();
mData = map;
initData();
initView(mContext);
}
public void setData(LinkedHashMap<Integer, String> map) {
mData = map;
initData();
initView(mContext);
}
public void setOnBannerItemClickListener(onBannerItemClickListener listener) {
mItemClickListener = listener;
if (mItemClickListener != null) {
myAdapter.setOnItemClickListener(new FxBannerAdapter.OnAdapterItemClickListener() {
@Override
public void onItemClick(View v) {
mItemClickListener.onItemClick(v);
Log.d("BannerView", "onClick");
}
});
}
}
/**
* 設置是否自動輪播
*
* @param b true or false
* @hide
*/
private void setAutoRotation(boolean b) {
isCycle = b;
}
private void setAutoRotationDuration(int mills) {
mDelayMillis = mills;
}
public FxBannerView(Context context) {
this(context, null);
mContext = context;
}
public FxBannerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
mContext = context;
}
public FxBannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
}
private void initData() {
imgs.clear();
strings.clear();
for (Object o : mData.entrySet()) {
Map.Entry entry = (Map.Entry) o;
imgs.add((int) entry.getKey());
strings.add((String) entry.getValue());
}
}
private void initView(Context context) {
View root = LayoutInflater.from(context).inflate(R.layout.banner_view, this);
mLlPointGroup = root.findViewById(R.id.ll_point_group);
mLlStatusBar = root.findViewById(R.id.ll_status_bar);
mTvName = root.findViewById(R.id.tv_name);
mViewpagerMain = root.findViewById(R.id.viewpager_main);
imageViews = new ArrayList<>();
if (myAdapter == null) {
return;
}
mLlStatusBar.setVisibility(VISIBLE);
mLlPointGroup.removeAllViews();
for (int i = 0; i < imgs.size(); i++) {
int imgSrc = imgs.get(i);
// 添加圖
ImageView imageView = new ImageView(context);
imageView.setBackgroundResource(imgSrc);
imageViews.add(imageView);
// 添加點
ImageView pointView = new ImageView(context);
pointView.setBackgroundResource(R.drawable.point_seletor);
// px轉dp
float scale = getResources().getDisplayMetrics().density;
int width = (int) (scale * 8 + 0.5f);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, width);
params.rightMargin = width;
pointView.setLayoutParams(params);
mLlPointGroup.addView(pointView);
if (i == 0) {
pointView.setEnabled(true);
} else {
pointView.setEnabled(false);
}
}
if (imageViews.isEmpty()) {
return;
}
myAdapter.setImageViews(imageViews);
mViewpagerMain.setAdapter(myAdapter);
myAdapter.notifyDataSetChanged();
mViewpagerMain.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
int realPosition = position % imageViews.size();
mTvName.setText(strings.get(realPosition));
mLlPointGroup.getChildAt(prePosition).setEnabled(false);
mLlPointGroup.getChildAt(realPosition).setEnabled(true);
prePosition = realPosition;
}
/**
當頁面滾動狀態變化的時候回調這個方法
靜止->滑動
滑動-->靜止
靜止-->拖拽
*/
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_DRAGGING) {
isCycle = true;
mHandler.removeCallbacksAndMessages(null);
} else if (state == ViewPager.SCROLL_STATE_IDLE && isCycle) {
isCycle = false;
mHandler.removeCallbacksAndMessages(null);
mHandler.sendEmptyMessageDelayed(0, mDelayMillis);
}
}
});
int midItem = Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % imageViews.size();
mViewpagerMain.setCurrentItem(midItem);
mTvName.setText(strings.get(prePosition));
mHandler.sendEmptyMessageDelayed(0, mDelayMillis);
}
public interface onBannerItemClickListener {
void onItemClick(View v);
}
}