常可以看见ViewPager翻页视图下有几个圆点,并随着图片变化而变化。
我们称之为横幅条,横幅条自定义控件的编写有两种:(1)使用Paint与Canvas绘制;(2)使用RadioButton组合。
第一种编写方法的优势是可以显示滑动过程中的位置,劣势是无法点击圆点。
第二种编写方法的优势是可以点击圆点,劣势是无法显示滑动过程中的位置。
一、使用Paint与Canvas绘制(自定义控件)
核心原理:
1. 重写Java构造方法(不使用可不重写)
2. 重写XML构造方法(及属性数组XML文件)(获取属性值)
3. 重写onMeasure()方法,获取尺寸(使用MeasureSpec.getMode()方法和MeasureSpec.getSize()方法),设置尺寸(使用setMeasuredDimension()方法)
4. 重写dispatchDraw()方法,绘制图像(大底色圆,小选中圆)
5. 创建其他设置UI参数方法(使用invalidate()方法进行即时重绘控件);如,设置选中按钮的方法、设置选中按钮及偏移量的方法、设置选中颜色的方法
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;
import android.widget.RelativeLayout;
/**
* 复制时请携带MyBar属性(数组XML文件)
*/
public class MyBar extends RelativeLayout {
int oCount;//数量
int oRDp;//半径dp
int oRPx;//半径px
int oSpacerDp;//间隔Dp
int oSpacerPx;//间隔Px
int viewWidth;//控件宽度
Context context;//环境
int[] oi;//底色圆心像素位置数组
int selectONum;//选中圆心Num
float selectOffset;//选中偏移量比例
int selectColor;//选中颜色
int unselectColor;//底色
float selectFloat;//选中圆占底色比例 0~1
float density;
//Java构造方法
public MyBar(Context context) {
super(context);
this.context=context;
getAttrs(context);
}
//XML构造方法
public MyBar(Context context, AttributeSet attrs) {
super(context, attrs);
//在XML布局文件中使用的方法
this.context=context;
getAttrs(context, attrs);
}
/**
* 获取XML属性
*/
@SuppressLint("ResourceAsColor")
private void getAttrs(Context context, AttributeSet attrs){
//获取属性值
TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.MyBar);
oCount=typedArray.getInt(R.styleable.MyBar_oCount,1);
oRDp=typedArray.getInt(R.styleable.MyBar_oRDp,5);
oSpacerDp=typedArray.getInt(R.styleable.MyBar_oSpacerDp,12);
selectONum=typedArray.getInt(R.styleable.MyBar_selectONum,1);
selectFloat=typedArray.getFloat(R.styleable.MyBar_selectFloat,0.7f);
selectColor=typedArray.getColor(R.styleable.MyBar_selectColor,Color.RED);
unselectColor=typedArray.getColor(R.styleable.MyBar_unselectColor,R.color.teal_700);
selectOffset=typedArray.getFloat(R.styleable.MyBar_selectOffset,0);
//修正错误参数
//选中数大于总数
if(selectONum>oCount){
selectONum=oCount;
selectOffset=0;
}
//间隔小于半径*2
if(oSpacerDp<oRDp*2){
oSpacerDp=oRDp*2;
}
//偏移量过大或过小
if(selectOffset<-1|selectOffset>1){
selectOffset=0;
}
//获取density
WindowManager windowManager= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics=new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
density= displayMetrics.density;
//获取圆像素大小
oRPx= (int) (oRDp*density);
//获取间隔像素大小
oSpacerPx= (int) (oSpacerDp*density);
//回收
typedArray.recycle();
}
/**
* Java构建方法的属性
*/
public void getAttrs(Context context){
//默认属性值
oCount=1;
oRDp=5;
oSpacerDp=12;
selectONum=1;
selectFloat=0.7f;
selectColor=Color.RED;
unselectColor=R.color.teal_700;
selectOffset=0;
//获取density
WindowManager windowManager= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics=new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
density= displayMetrics.density;
//获取圆像素大小
oRPx= (int) (oRDp*density);
//获取间隔像素大小
oSpacerPx= (int) (oSpacerDp*density);
}
/**
* 获取尺寸,修改尺寸
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width=MeasureSpec.getSize(widthMeasureSpec);
int height=MeasureSpec.getSize(heightMeasureSpec);
//Log.d("OK",MeasureSpec.getMode(widthMeasureSpec)+"&&&"+MeasureSpec.getMode(heightMeasureSpec));
if(MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.UNSPECIFIED){
//具体值--
width=oSpacerPx*(oCount-1)+oRPx*2;
}
else if (MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.AT_MOST) {
//wrap--
width=oSpacerPx*(oCount-1)+oRPx*2;
}
else {
//match--具体值
}
if(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED){
//具体值--
height=oRPx*2;
}
else if(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.AT_MOST){
//wrap--
height=oRPx*2;
}
else {
//match--具体值
}
setMeasuredDimension(width,height);
}
/**
* 绘制图像
*/
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
//获取控件宽度
viewWidth=getWidth();
//圆心像素位置数组
oi=new int[oCount];
int oi1=(viewWidth-oSpacerPx*(oCount-1))/2;
for (int i=0;i<oCount;i++){
oi[i]=oi1;
oi1=oi1+oSpacerPx;
}
//绘制底色
Paint paint=getPaint(unselectColor);
for(int i=0;i<oi.length;i++){
canvas.drawCircle(oi[i],oRPx,oRPx,paint);
}
//绘制选中
paint=getPaint(selectColor);
canvas.drawCircle(oi[selectONum-1]+oSpacerPx*selectOffset,oRPx,oRPx*selectFloat,paint);
}
/**
* 设置圆数量
* @param oCount
*/
protected void setoCount(int oCount){
this.oCount=oCount;
if(selectONum>oCount){
selectONum=oCount;
}
//立即重绘
invalidate();
}
/**
* 设置圆半径
* @param oRDp 半径dp值
*/
protected void setoRDp(int oRDp){
if(oRDp*2>this.oSpacerDp){
this.oSpacerDp=oRDp*2;
this.oSpacerPx= (int) (oSpacerDp*density);
}
this.oRDp=oRDp;
this.oRPx= (int) (oRDp*density);
//立即重绘
invalidate();
}
/**
* 设置圆心间隔
* @param oSpacerDp 间隔dp值
*/
protected void setoSpacerDp(int oSpacerDp){
if(oSpacerDp<this.oRDp*2){
oSpacerDp=this.oRDp*2;
}
this.oSpacerDp=oSpacerDp;
this.oSpacerPx= (int) (oSpacerDp*density);
//立即重绘
invalidate();
}
/**
* 设置选中
* @param num 选中的圆位置
*/
protected void setSelect(int num){
if(num>oCount){
num=oCount;
}
this.selectONum=num;
this.selectOffset=0;
//立即重绘
invalidate();
}
/**
* 设置选中
* @param num 选中的圆位置
* @param selectOffset 偏移量比例 -1~1
*/
protected void setSelect(int num,float selectOffset){
if(num>oCount){
num=oCount;
}
this.selectONum=num;
this.selectOffset=selectOffset;
//立即重绘
invalidate();
}
/**
* 获取指定颜色画笔
*/
private Paint getPaint(int color){
Paint paint=new Paint();
//设置画笔
paint.setColor(color);
//抗锯齿
paint.setAntiAlias(true);
return paint;
}
/**
* 设置选中圆点颜色
* @param color
*/
private void setSelectColor(int color){
selectColor=color;
//立即重绘
invalidate();
}
/**
* 设置未选中圆底色
* @param color
*/
private void setUnselectColor(int color){
unselectColor=color;
//立即重绘
invalidate();
}
}
<!-- 属性数组XML文件 !-->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyBar">
<attr name="oCount" format="integer"/><!-- 圆数量 !-->
<attr name="oRDp" format="integer"/><!-- 圆半径dp !-->
<attr name="oSpacerDp" format="integer"/><!-- 圆心间隔dp !-->
<attr name="selectONum" format="integer"/><!-- 选中圆(1开始) !-->
<attr name="selectFloat" format="float"/><!-- 选中圆占原圆大小 !-->
<attr name="selectColor" format="color"/><!-- 选中圆颜色 !-->
<attr name="unselectColor" format="color"/><!-- 未选中圆底色 !-->
<attr name="selectOffset" format="float"/><!-- 偏移量(-1~1) !-->
</declare-styleable>
</resources>
在XML布局中使用:
<!-- 自定义控件MyBar的使用 !-->
<xxx.MyBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:oCount="10"
app:selectONum="4"
app:oRDp="9"
app:oSpacerDp="20"
/>
二、使用RadioButton组合(组合自定义控件)
核心原理:
1. 创建包含ViewPager和RadioGroup的XML布局文件
2. 重写Java构造方法(不使用可不重写)
3. 重写XML构造方法(及属性数组XML文件)(获取属性值,向单选组添加单选按钮)
4. 重写onMeasure()方法,获取尺寸(使用MeasureSpec.getMode()方法和MeasureSpec.getSize()方法),设置尺寸(使用setMeasuredDimension()方法)
5. 创建设置翻页适配器的方法,该方法需要在设置适配器的同时修改按钮数(初始化按钮);并为单选组及翻页视图设置监听器,在点击单选按钮时修改翻页视图,在翻页时修改选中的单选按钮
6. 创建其他设置UI参数方法(使用invalidate()方法进行即时重绘控件);如,设置选中按钮的方法、设置按钮数的方法
<!-- XML布局文件 !-->
<!-- bar_view_pager_layout.xml !-->
<?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"
xmlns:tools="http://schemas.android.com/tools">
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck"/>
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="match_parent"
android:layout_height="30dp"
android:paddingBottom="5dp"
android:background="#00BB86FC"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:gravity="center_horizontal"/>
</RelativeLayout>
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RelativeLayout;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
/**
* 复制时请携带
* MyBarViewPager属性(数组XML文件)
* 组合控件布局文件(bar_view_pager_layout.xml)
*/
public class MyBarViewPager extends RelativeLayout {
int buttonCount;//按钮数
View nowView;//当前控件
int width;//控件宽px
int height;//控件高px
RadioGroup radioGroup;//单选组
RadioButton[] radioButtons;//单选按钮数组
ViewPager viewPager;//翻页视图
Context context;
boolean haveAdapter=false;//是否设置适配器
/**
* Java构造方法
*/
public MyBarViewPager(Context context) {
super(context);
//获取属性
getAttrs(context);
//添加布局
nowView=LayoutInflater.from(context).inflate(R.layout.bar_view_pager_layout,null,false);
addView(nowView);
//添加按钮
initRadioButton();
}
/**
* XML构造方法
*/
public MyBarViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.context=context;
//获取属性
getAttrs(context, attrs);
//添加布局
nowView=LayoutInflater.from(context).inflate(R.layout.bar_view_pager_layout,null,false);
addView(nowView);
//添加按钮
initRadioButton();
}
/**
* 获取XML属性
*/
private void getAttrs(Context context, AttributeSet attrs){
TypedArray typedArray= context.obtainStyledAttributes(attrs,R.styleable.MyBarViewPager);
buttonCount=typedArray.getInt(R.styleable.MyBarViewPager_buttonCount,1);
}
/**
* Java构建方法的属性
*/
private void getAttrs(Context context){
buttonCount=1;
}
/**
* 设置尺寸
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width=MeasureSpec.getSize(widthMeasureSpec);
height=MeasureSpec.getSize(heightMeasureSpec);
//设置最小宽高值
WindowManager windowManager= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics=new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
float density=displayMetrics.density;
int minWidth= (int) (5*density*buttonCount);
int minHeight= (int) (30*density);
if(height<minHeight){
height=minHeight;
}
if(width<minWidth){
width=minWidth;
}
setMeasuredDimension(width,height);
}
/**
* 初始化按钮
*/
private void initRadioButton(){
//获取单选组
radioGroup=nowView.findViewById(R.id.radioGroup);
//创建按钮数组
radioButtons=new RadioButton[buttonCount];
//添加按钮
for(int i=0;i<buttonCount;i++){
RadioButton radioButton=new RadioButton(context);
radioButtons[i]=radioButton;
radioGroup.addView(radioButton);
}
//默认选中
radioButtons[0].setChecked(true);
}
/**
* 设置适配器
* @param pagerAdapter 翻页适配器
*/
protected void setPagerAdapter(PagerAdapter pagerAdapter){
if(pagerAdapter==null|pagerAdapter.getCount()<1){
return;
}
haveAdapter=true;
//移除按钮
radioGroup.removeAllViews();
//添加按钮
buttonCount=pagerAdapter.getCount();
initRadioButton();
//获取翻页视图
viewPager=nowView.findViewById(R.id.viewPager);
//设置适配器
viewPager.setAdapter(pagerAdapter);
//按钮关联翻页视图
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
public void onCheckedChanged(RadioGroup radioGroup, int i) {
viewPager.setCurrentItem(i);
}
});
//翻页视图关联按钮
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener(){
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
public void onPageSelected(int position) {
radioButtons[position].setSelected(true);
}
public void onPageScrollStateChanged(int state) {
}
});
}
/**
* 设置选中按钮
* @param num 选中按钮位置(1开始)
*/
protected void setSelected(int num){
if(num>radioButtons.length){
num=radioButtons.length;
}
radioButtons[num-1].setSelected(true);
if(haveAdapter){
viewPager.setCurrentItem(num-1);
}
}
/**
* 设置按钮数(在设置适配器后不生效)
* @param buttonCount
*/
protected void setButtonCount(int buttonCount) {
if (!haveAdapter){
this.buttonCount = buttonCount;
//立即重绘
invalidate();
}
}
/**
* 判断是否已设置翻页适配器
* @return
*/
public boolean havePagerAdapter(){
return haveAdapter;
}
}
<!-- 属性数组XML文件 !-->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyBarViewPager">
<attr name="buttonCount" format="integer"/> <!-- 按钮数 !-->
</declare-styleable>
</resources>
在XML布局中使用:
<!-- 自定义控件MyBarViewPager的使用 !-->
<xxx.MyBarViewPager
android:layout_width="match_parent"
android:layout_height="200dp"
app:buttonCount="5"
android:background="@color/purple_200"
/>
tag:横幅条,RadioGroup,RadioButton,ViewPager,自定义控件,自定义组合控件,组合控件,Paint,Canvas