Android 基础-------自定义控件

本文介绍了Android中自定义控件的三种方法:1)原生控件扩展,通过继承并重写特定方法实现;2)组合控件,整合多个原生控件并用自定义属性和接口回调进行设置;3)纯自定义控件,针对现有控件无法满足需求的情况,从View或ViewGroup继承来创建全新的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


关于Android 的自定义控件    


在Android 中自定义控件可以分为三种  

1.android 原生控件的扩展

2.组合控件(增加新的属性等)

3.通过重写onMearse  onLayout onDraw以及OnTouch  完全自定义的全新控件  




一  原生扩展空间  

 该种模式的自定义控件是 由于原生控件无法完全满足需求,从而对对原本控件进行扩展   。

通常自定义一个空间 去继承需要扩展的控件,对onDraw onTouch 进行扩写 或者 重写设计事件分发机制,对事件分发的三个函数 进行修改 (dispatchTouchEvent   onInterceptTouchEvent    onTouchEvent)  


示例代码:

/**
 * 简单的自定义控件  继承Android 的TextView 控件 进行扩充
 * 该控件的ondraw方法保留了TextView 中的部分
 */
public class MyTextView  extends TextView{


    private int mViewWith;
    private LinearGradient linearGradient;
    private Matrix matrix;
    private int translate;

    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if(mViewWith==0){
            mViewWith=getMeasuredWidth();
            if(mViewWith>0){
                //getPaint  获取绘制text的 画笔
                Paint paint = getPaint();
                linearGradient =new LinearGradient(0,0,mViewWith,0,new int[]{Color.BLUE,0xffffffff,Color.BLUE},null, Shader.TileMode.CLAMP);
                paint.setShader(linearGradient);
                matrix = new Matrix();
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //在super之前进行操作  是在原本的TextView 绘制文字之前进行其他绘制



        //super  正常的绘制文字
        super.onDraw(canvas);
        //super 之后执行   绘制完文字之后进行处理
        if(matrix!=null){
            translate+=mViewWith/5;
            if(translate>2*mViewWith){
                translate=-mViewWith;
            }
            matrix.setTranslate(translate,0);
            linearGradient.setLocalMatrix(matrix);
            postInvalidateDelayed(100);
        }


    }
}



二 组合控件

该种模式的自定义控件主要是将原有的控件进行整合   

该种方式  通过加载xml的方式 将多个原生控件整合在一起,使用自定义属性的方式队员友控件进行设置  通过自定义接口 进行回掉


示例代码:

/**
 * Created by xgd6612 on 2016/12/25.
 * 简单的组合空间  这里只是简单的进行了控件的组合使用
 */

public class MyTitleBar extends RelativeLayout implements View.OnClickListener {

    private String titleName;
    private TextViewOnClickListener textViewOnClickListener;



    public interface TextViewOnClickListener{
        void LeftClick();
    }


    public  void setTextViewOnClickListener(TextViewOnClickListener listener){
        textViewOnClickListener=listener;
    }
    public MyTitleBar(Context context) {
        super(context);
    }



    public MyTitleBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        View view = View.inflate(context, R.layout.MyTitleBar, this);
        TextView title = (TextView) view.findViewById(R.id.tv_title);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTitleBar);
        //在这里   typedArray  通过使用getXXX 的方法进行属性获取,第一个参数是属性名   如果有第二个参数的话 第二个参数返回的是默认值
        titleName=typedArray.getString(R.styleable.MyTitleBar_title);
        title.setText(titleName);
        title.setOnClickListener(this);
        typedArray.recycle();
    }

    public MyTitleBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }



    @Override
    public void onClick(View v) {
        int id=v.getId();
        if(id==R.id.tv_title){
            textViewOnClickListener.LeftClick();
        }
    }


}

在使用自定义属性的时候  需要引入命名空间  

  xmlns:mytitle="http://schemas.android.com/apk/res-auto"

在这里 mytitle 是随意命名的但是命名结束后在使用自定义属性的时候需要使用这个命名作为前缀   类似于android:




三、 纯自定义控件

这里自定义控件是既不是在原有空间的基础上进行扩充(完全保留院士功能),又不是对多种原生控件进行组合使用 。

而是当现有的控件 无法满足需求的时候完全自定义的控件。

这类空间中一般继承view 或者viewGroup 


示例:

1.折线图-----继承View

public class LineChatView extends View {
    //定义变量月份 12个月
    private final String[] MONTH = new String[]{"","","","","","","","","","","",""};
    //定义纵坐标
    private String[] yTitlesStrings = {"10", "8", "6", "4", "2"};
    //宽度
    private int width;
    //高度
    private int height;
    //左宽
    private int leftWidth;
    //左高
    private int leftHeight;

    private int firstStep;

    private int columnWidth;
    //自定义接口
    private OnSelctMonthListener onSelctMonthListener;
    //选择月份
    private int selectMonth = -1;
    //
    private double ratio;
    //线程池
    private ExecutorService pool;
    private boolean stopThread;
    //单位
    private String units = "元";
    private float startHeight;
    private int tag;

    //选中月份
    private int selectTextMonth = -1;
    private int radio = 10;
    private double[] mData = new double[12];
    //
    private double[] mRadio = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
    //  坐标轴水平内部 虚线画笔
    private Paint hLinePaint;
    //  坐标轴  画笔
    private Paint xyPaint;
    //  绘制文本的 画笔
    private Paint titlePaint;
    //  圆环画笔
    private Paint recPaint;
    //折线画笔
    private Paint linePaint;
    //白色画笔
    private Paint whitePaint;
    //选中点画笔
    private Paint selectPaint;
    //y轴坐标
    private String[][] ytitile = {{"10", "8", "6", "4", "2"}, {"5", "4", "3", "2", "1"}, {"2.5", "2", "1.5", "1", "0.5"}, {"1000", "800", "600", "400", "200"}, {"500", "400", "300", "200", "100"}, {"250", "200", "150", "100", "50"}, {"100", "80", "60", "40", "20"}, {"50", "40", "30", "20", "10"}, {"25", "20", "15", "10", "5"}, {"10000", "8000", "6000", "4000", "2000"}, {"5000", "4000", "3000", "2000", "1000"}, {"2500", "2000", "1500", "1000", "500"}};

    //构造函数
    public LineChatView(Context context) {
        super(context);
        init();
    }

    //构造函数
    public LineChatView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    //构造函数
    public LineChatView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    //也是一个构造函数
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public LineChatView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    //确定月份的 传入一个月份
    public void setSelectMonth(int selectMonth) {
        this.selectMonth = selectMonth;
    }

    //一个监听的方法 传入一个接口实现
    public LineChatView setOnSelctMonthListener(OnSelctMonthListener onSelctMonthListener) {
        this.onSelctMonthListener = onSelctMonthListener;
        return this;
    }

    //初始化
    private void init() {
        hLinePaint = new Paint();
        titlePaint = new Paint();
        linePaint = new Paint();
        recPaint = new Paint();
        whitePaint = new Paint();
        selectPaint = new Paint();
        xyPaint = new Paint();
        //设置抗锯齿
        whitePaint.setAntiAlias(true);
        recPaint.setAntiAlias(true);
        linePaint.setAntiAlias(true);
        titlePaint.setAntiAlias(true);
//        hLinePaint.setAntiAlias(true);
        xyPaint.setAntiAlias(true);
        //设置画笔颜色

        recPaint.setColor(Color.parseColor("#0570e9"));
        xyPaint.setColor(Color.parseColor("#8f8f8f"));
        whitePaint.setColor(Color.parseColor("#FFFFFF"));
        hLinePaint.setColor(Color.parseColor("#ffd8d8d8"));
        titlePaint.setColor(Color.parseColor("#999999"));
        linePaint.setColor(Color.parseColor("#0570e9"));
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(3);

        //指定一个线程的线程池
        pool = Executors.newFixedThreadPool(1);
    }

    public void setmData(final double[] mData) {
        stopThread = true;
        this.mData = mData;

        final double max = max(mData);
        units = "元";
        if (max > 100000000) {
            units = "亿元";
            if (max > 500000000) {
                ratio = 100000000;
                yTitlesStrings = ytitile[0];
            } else if (max > 250000000) {
                ratio = 50000000;
                yTitlesStrings = ytitile[1];
            } else {
                ratio = 25000000;
                yTitlesStrings = ytitile[2];
            }
        } else if (max > 10000000) {
            units = "万元";
            if (max > 50000000) {
                ratio = 10000000;
                yTitlesStrings = ytitile[9];
            } else if (max > 25000000) {
                ratio = 5000000;
                yTitlesStrings = ytitile[10];
            } else {
                ratio = 2500000;
                yTitlesStrings = ytitile[11];
            }
        } else if (max > 1000000) {
            units = "万元";
            if (max > 5000000) {
                ratio = 1000000;
                yTitlesStrings = ytitile[3];
            } else if (max > 2500000) {
                ratio = 500000;
                yTitlesStrings = ytitile[4];
            } else {
                ratio = 250000;
                yTitlesStrings = ytitile[5];
            }
        } else if (max > 100000) {
            units =  "万元";
            if (max > 500000) {
                ratio = 100000;
                yTitlesStrings = ytitile[6];
            } else if (max > 250000) {
                ratio = 50000;
                yTitlesStrings = ytitile[7];
            } else {
                ratio = 25000;
                yTitlesStrings = ytitile[8];
            }
        } else if (max > 10000) {
            units =  "万元";
            if (max > 50000) {
                ratio = 10000;
                yTitlesStrings = ytitile[0];
            } else if (max > 25000) {
                ratio = 5000;
                yTitlesStrings = ytitile[1];
            } else {
                ratio = 2500;
                yTitlesStrings = ytitile[2];
            }
        } else if (max > 1000) {
            units =  "千元";
            if (max > 5000) {
                ratio = 1000;
                yTitlesStrings = ytitile[0];
            } else if (max > 2500) {
                ratio = 500;
                yTitlesStrings = ytitile[1];
            } else {
                ratio = 250;
                yTitlesStrings = ytitile[2];
            }
        } else if (max > 100) {
            units =  "元";
            if (max > 500) {
                ratio = 100;
                yTitlesStrings = ytitile[3];
            } else if (max > 250) {
                ratio = 50;
                yTitlesStrings = ytitile[4];
            } else {
                ratio = 25;
                yTitlesStrings = ytitile[5];
            }
        } else if (max > 10) {
            units =  "元";
            if (max > 50) {
                ratio = 10;
                yTitlesStrings = ytitile[6];
            } else if (max > 25) {
                ratio = 5;
                yTitlesStrings = ytitile[7];
            } else {
                ratio = 2.5;
                yTitlesStrings = ytitile[8];
            }
        } else {
            ratio = 1;
            units =  "元";
            yTitlesStrings = ytitile[0];
        }


        final double[] sub = new double[mData.length];
        final double[] MyNumbles = new double[mData.length];
        for (int i = 0; i < MyNumbles.length; i++) {
            MyNumbles[i] = 1.0 - mData[i] * 1.0 / ratio / 10;
            sub[i] = (MyNumbles[i] - mRadio[i]) / 300;
        }
        SystemClock.sleep(2);
        stopThread = false;
        pool.execute(new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 300; i++) {
                    if (stopThread) {
                        return;
                    }
                    for (int j = 0; j < mRadio.length; j++) {
                        mRadio[j] += sub[j];
                        if (i == 299 && mData[j] == 0) {
                            mRadio[j] = 1;
                        }
                    }
                    SystemClock.sleep(1);
                    postInvalidate();  //可以子线程 更新视图的方法调用。
                }
            }
        });
    }

    @Override
    protected void onDraw(Canvas canvas) {
        width = getWidth();
        height = getHeight();
        leftWidth = width / 15;
        int rightWidth = width / 24;
        columnWidth = (width - leftWidth - rightWidth) / MONTH.length;
        int textSize = (int) (columnWidth / 2.5);
        leftHeight = (int) (height - textSize * 3.5);
        startHeight = (float) (textSize * 2);
        titlePaint.setTextAlign(Paint.Align.LEFT);
        canvas.drawText("单位:" + units, leftWidth, textSize, titlePaint);
        //画y轴
        canvas.drawLine(leftWidth, 0, leftWidth, leftHeight, hLinePaint);
        //画y坐标
        int hPerHeight = (leftHeight - height / 60) / yTitlesStrings.length;
        titlePaint.setTextSize(textSize);
        titlePaint.setTextAlign(Paint.Align.CENTER);
        for (int i = 0; i < yTitlesStrings.length; i++) {
            canvas.drawText(yTitlesStrings[i], leftWidth / 2, i * hPerHeight + height / 30 + startHeight, titlePaint);
            canvas.drawLine(leftWidth, i * hPerHeight + height / 60 + startHeight,
                    width - rightWidth, i * hPerHeight + height / 60 + startHeight, hLinePaint);
        }
        //画x轴
        canvas.drawLine(rightWidth, leftHeight + startHeight, width - rightWidth, leftHeight + startHeight, xyPaint);
        //画x坐标
        titlePaint.setTextSize(textSize);
        firstStep = columnWidth / 2;
        for (int i = 0; i < MONTH.length; i++) {
            canvas.drawText(MONTH[i].substring(0, MONTH[i].length() > 3 ? 3 : MONTH[i].length()),
                    leftWidth + firstStep + columnWidth * i, (float) (height - 0.1 * textSize), titlePaint);
        }
        //画线
        for (int i = 0; i < MONTH.length; i++) {
            float circleHeight = 0;
            double mRadio = this.mRadio[i];
            circleHeight = (float) ((leftHeight - height / 60) * mRadio) + height / 60 + startHeight;
            //画线
            if (i < MONTH.length - 1) {
                float circleHeight2 = 0;
                mRadio = this.mRadio[i + 1];
                circleHeight2 = (float) ((leftHeight - height / 60) * mRadio) + height / 60 + startHeight;
                canvas.drawLine(leftWidth + firstStep + columnWidth * i, circleHeight,
                        leftWidth + firstStep + columnWidth * (i + 1), circleHeight2, linePaint);
            }
        }
        //画点
        for (int i = 0; i < MONTH.length; i++) {
            float circleHeight = 0;
            double mRadio = this.mRadio[i];
            circleHeight = (float) ((leftHeight - height / 60) * mRadio) + height / 60 + startHeight;
            //画点
            int circleWidth = px2dip(getContext(), 4);
            if (mRadio != 1) {
                if (i + 1 == selectMonth) {//高亮
                    RadialGradient mRadialGradient = new RadialGradient(leftWidth + firstStep + columnWidth * i, circleHeight, px2dip(getContext(), 10), new int[]{
                            Color.argb(100, 0, 0, 255), Color.argb(0, 0, 0, 255)}, null,
                            Shader.TileMode.REPEAT);
                    selectPaint.setShader(mRadialGradient);
                    canvas.drawCircle(leftWidth + firstStep + columnWidth * i, circleHeight, px2dip(getContext(), 10), selectPaint);
                    circleWidth = px2dip(getContext(), 5);
                }
                canvas.drawCircle(leftWidth + firstStep + columnWidth * i, circleHeight, circleWidth, recPaint);
                canvas.drawCircle(leftWidth + firstStep + columnWidth * i, circleHeight, px2dip(getContext(), 3), whitePaint);
            }
        }
    }



    //自定义 点击触摸事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                for (int i = 0; i < MONTH.length; i++) {
                    float circleHeight = 0;
                    double mRadio = this.mRadio[i];
                    circleHeight = (float) ((leftHeight - height / 60) * mRadio) + height / 60 + startHeight;
                    //画点
//                   canvas.drawCircle(leftWidth+firstStep+columnWidth*i,circleHeight,10,recPaint);
                    int circleX = leftWidth + firstStep + columnWidth * i;
                    float textHeight = (float) (height - 0.1 * (columnWidth / 2.5));
                    //(float) (height-0.1*textSize)  y轴坐标
                    if (event.getX() > circleX - 30 && event.getX() < circleX + 30 && event.getY() > circleHeight - 30 && event.getY() < circleHeight + 30) {
                        if (this.mData[i] != 0) {
                            selectMonth = i + 1;
                            postInvalidate();
                        }
                        if (onSelctMonthListener != null) {
                            tag = 1;
                        }
                    } else if (event.getX() > circleX - 30 && event.getX() < circleX + 30 && event.getY() > textHeight - 40 && event.getY() < textHeight + 40) {
                        if (this.mData[i] != 0) {
                            selectMonth = i + 1;
                            postInvalidate();
                        }
                        tag = 2;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (tag == 1) {
                    for (int i = 0; i < MONTH.length; i++) {
                        if (this.mData[i] != 0) {
                            float circleHeight = 0;
                            double mRadio = this.mRadio[i];
                            circleHeight = (float) ((leftHeight - height / 60) * mRadio) + height / 60 + startHeight;
                            int circleX = leftWidth + firstStep + columnWidth * i;
                            if (event.getX() > circleX - 30 && event.getX() < circleX + 30 && event.getY() > circleHeight - 50 && event.getY() < circleHeight + 50) {
                                selectMonth = i + 1;
                                postInvalidate();
                                onSelctMonthListener.selectMonth(selectMonth);
                                tag = 0;
                            }
                        }
                    }

                }
                if (tag == 2) {
                    for (int i = 0; i < MONTH.length; i++) {
                        if (this.mData[i] != 0) {
                            double mRadio = this.mRadio[i];
                            float textHeight = (float) (height - 0.1 * (columnWidth / 2.5));
                            int circleX = leftWidth + firstStep + columnWidth * i;
                            if (event.getX() > circleX - 30 && event.getX() < circleX + 30 && event.getY() > textHeight - 40 && event.getY() < textHeight + 40) {
                                selectMonth = i + 1;
                                postInvalidate();
                                onSelctMonthListener.selectMonth(selectMonth);
                                tag = 0;
                            }
                        }
                    }
                    break;
                }
        }
        return true;
    }

    private double max(double[] arr) {
        double max = 0;
        if (arr != null) {
            max = arr[0];
            for (Double i : arr) {
                if (i > max) {
                    max = i;
                }
            }
        }
        return max;
    }


    //定义接口
    public interface OnSelctMonthListener {
        void selectMonth(int i);
    }

    //定义接口
    public interface onSelctTextMonthListener {
        void selectMonth(int i);
    }

}


2.仿ScrollView  ---继承ViewGroup 

public class MyGroupView extends ViewGroup {


    private int mScreenHeight;
    private int lastY;
    private int start;
    private int endY;
    private int dScrollY;
    private Scroller mScroller;

    public MyGroupView(Context context) {
        super(context);
        init(context);
    }



    public MyGroupView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MyGroupView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public MyGroupView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }
    
    private void init(Context context) {
        mScreenHeight=getResources().getDisplayMetrics().heightPixels;
        mScroller=new Scroller(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count=getChildCount();
        //使用遍历的方式进行子view的测量
        int i=0;
        for(;i<count;i++){
            View childView =getChildAt(i);
            measureChild(childView,widthMeasureSpec,heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int  childCount =getChildCount();

        MarginLayoutParams marginLayoutParams = (MarginLayoutParams) getLayoutParams();
        marginLayoutParams.height=mScreenHeight+childCount;
        setLayoutParams(marginLayoutParams);

        int i=0;
        for(;i<childCount;i++){
            View child =getChildAt(i);
            if(child.getVisibility()!= View.GONE){
                child.layout(l,i*mScreenHeight,r,(i+1)*mScreenHeight);
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int y= (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastY=y;
                start=getScrollY();
                break;
            case MotionEvent.ACTION_MOVE:
                if(mScroller.isFinished()){
                    mScroller.abortAnimation();
                }
                int dy = lastY-y;
                if(getScrollY()<0){
                    dy=0;
                }
                if(getScrollY()>getHeight()-mScreenHeight){
                    dy=0;
                }
                scrollBy(dy,0);
                lastY=y;
                break;
            case MotionEvent.ACTION_UP:
                endY=getScrollY();
                dScrollY=endY-start;
                if(dScrollY>0){
                    if(dScrollY<mScreenHeight/3){
                        mScroller.startScroll(0, getScrollY(),0,-dScrollY);
                    }else{
                        mScroller.startScroll(0, getScrollY(),0,mScreenHeight-dScrollY);
                    }
                }else{
                    if(-dScrollY<mScreenHeight/3){
                        mScroller.startScroll(0, getScrollY(),0,-dScrollY);
                    }else{
                        mScroller.startScroll(0, getScrollY(),0,-mScreenHeight-dScrollY);
                    }
                }
                break;
        }
        postInvalidate();
        return true;

    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if(mScroller.computeScrollOffset()){
            scrollTo(0,mScroller.getCurrY());
            postInvalidate();
        }
    }
}




以上为基本的三大类自定义控件的简单使用,在实际使用的时候会根据业务需求进行更多的扩充,以及对事件分发的处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值