横幅条(自定义控件)的编写

常可以看见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. 创建包含ViewPagerRadioGroupXML布局文件

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

  • 15
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在下嗷呜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值