自定义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);
}
}