zoukankan      html  css  js  c++  java
  • Android自定义控件

    自定义控件的步骤:

    1. 首先写一个类,就是给控件起个名字
    2. 要在res/values目录下建立attrs.xml文件,写下需要定义的属性
    3. 在自定义类,包含AttributeSet参数的构造方法中,关联自定义属性
    4. 将自定义的控件类放在布局文件中
    5. 在视图类中使用

    正文内容如下:

    1、继承View使用canvas绘制实例,自定义一个TextView

    public class CustomTextView extends View{
        
        private Paint mPaint;//画笔
        
        private int backColor;//背景色
    
        private int textColor;//文字颜色
        
        private float textSize;//文字大小
        
        private String textContent;//文字内容
        
        public CustomTextView(Context context) {
            super(context);
        }
        
        //关联自定义属性
        public CustomTextView(Context context,AttributeSet attr) {
            super(context);
            TypedArray array = context.obtainStyledAttributes(attr, R.styleable.CustomTextView);
            textColor = array.getColor(R.styleable.CustomTextView_textColor, 0X000000);
            backColor = array.getColor(R.styleable.CustomTextView_backColor, 0XFFFFFF);
            textSize = array.getDimension(R.styleable.CustomTextView_textSize, 32);
            textContent = array.getString(R.styleable.CustomTextView_textContent);
            array.recycle();
        }
        
        //开始绘画
        @SuppressLint("DrawAllocation")
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPaint = new Paint();
            mPaint.setStyle(Style.FILL);//填充方式
            mPaint.setTextSize(textSize);
            mPaint.setColor(backColor);
            canvas.drawRect(new Rect(10,10,200,100), mPaint);
            mPaint.setColor(textColor);
            canvas.drawText(textContent, 20, 60, mPaint);
        }
    }

     attrs.xml文件内容如下:

    <declare-styleable name="CustomTextView">
        <attr name="backColor" format="color" />
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="textContent" format="string" />
    </declare-styleable>

    2、继承ImageView实例,自定义一个圆形图片,适合做头像用

    类,属性,构造方法如下:

    public class RoundImageView extends ImageView
    private int mBorderThickness = 0;//边框厚度
    
    private int mBorderOutsideColor = 0;//外边框颜色
    
    private int mBorderInsideColor = 0;//内边框颜色
    
    private int defaultColor = 0xFFFFFF;//默认使用颜色
    
    private int defaultWidth = 0;//图片宽度
    
    private int defaultHeight = 0;//图片高度
    
    public RoundImageView(Context context) {
        super(context);
    }
    
    public RoundImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setCustomAttributes(context, attrs);
    }
    
    public RoundImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setCustomAttributes(context, attrs);
    }
    
    private void setCustomAttributes(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, 
                R.styleable.roundedimageview);
        mBorderThickness = array.getDimensionPixelSize(
                R.styleable.roundedimageview_border_thickness, 0);
        mBorderOutsideColor = array
                .getColor(R.styleable.roundedimageview_border_outside_color,
                        defaultColor);
        mBorderInsideColor = array.getColor(
                R.styleable.roundedimageview_border_inside_color, defaultColor);
        array.recycle();
    }

    边缘画圆方法:

    private void drawCircleBorder(Canvas canvas, int radius, int color) {
        Paint paint = new Paint();
        /* 去锯齿 */
        paint.setAntiAlias(true);
        paint.setFilterBitmap(true);
        paint.setDither(true);
        paint.setColor(color);
        /* 设置paint的 style 为STROKE:空心 */
        paint.setStyle(Paint.Style.STROKE);
        /* 设置paint的外框宽度 */
        paint.setStrokeWidth(mBorderThickness);
        canvas.drawCircle(defaultWidth / 2, defaultHeight / 2, radius, paint);
    }

    主要的绘制方法,获取裁剪圆形图片方法

    @Override
    protected void onDraw(Canvas canvas) {
        Drawable drawable = getDrawable();
        if (drawable == null) {
            return;
        }
        if (getWidth() == 0 || getHeight() == 0) {
            return;
        }
        this.measure(0, 0);
        if (drawable.getClass() == NinePatchDrawable.class)
            return;
        Bitmap b = ((BitmapDrawable) drawable).getBitmap();
        Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true);
        if (defaultWidth == 0) {
            defaultWidth = getWidth();
        }
        if (defaultHeight == 0) {
            defaultHeight = getHeight();
        }
        int radius = 0;
        if (mBorderInsideColor != defaultColor
                && mBorderOutsideColor != defaultColor) {
            // 定义画两个边框,分别为外圆边框和内圆边框
            radius = (defaultWidth < defaultHeight ? defaultWidth
                    : defaultHeight) / 2 - 2 * mBorderThickness;
            // 画内圆
            drawCircleBorder(canvas, radius + mBorderThickness / 2,
                    mBorderInsideColor);
            // 画外圆
            drawCircleBorder(canvas, radius + mBorderThickness
                    + mBorderThickness / 2, mBorderOutsideColor);
        } else if (mBorderInsideColor != defaultColor
                && mBorderOutsideColor == defaultColor) {
            // 定义画一个边框
            radius = (defaultWidth < defaultHeight ? defaultWidth
                    : defaultHeight) / 2 - mBorderThickness;
            drawCircleBorder(canvas, radius + mBorderThickness / 2,
                    mBorderInsideColor);
        } else if (mBorderInsideColor == defaultColor
                && mBorderOutsideColor != defaultColor) {
            // 定义画一个边框
            radius = (defaultWidth < defaultHeight ? defaultWidth
                    : defaultHeight) / 2 - mBorderThickness;
            drawCircleBorder(canvas, radius + mBorderThickness / 2,
                    mBorderOutsideColor);
        } else {
            // 没有边框
            radius = (defaultWidth < defaultHeight ? defaultWidth
                    : defaultHeight) / 2;
        }
        Bitmap roundBitmap = getCroppedRoundBitmap(bitmap, radius);
        canvas.drawBitmap(roundBitmap, defaultWidth / 2 - radius, defaultHeight
                / 2 - radius, null);
    }
    
    /**
     * 获取裁剪后的圆形图片
     */
    public Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) {
        Bitmap scaledSrcBmp;
        int diameter = radius * 2;
        // 为了防止宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片
        int bmpWidth = bmp.getWidth();
        int bmpHeight = bmp.getHeight();
        int squareWidth = 0, squareHeight = 0;
        int x = 0, y = 0;
        Bitmap squareBitmap;
        if (bmpHeight > bmpWidth) {// 高大于宽
            squareWidth = squareHeight = bmpWidth;
            x = 0;
            y = (bmpHeight - bmpWidth) / 2;
            // 截取正方形图片
            squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth,
                    squareHeight);
        } else if (bmpHeight < bmpWidth) {// 宽大于高
            squareWidth = squareHeight = bmpHeight;
            x = (bmpWidth - bmpHeight) / 2;
            y = 0;
            squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth,
                    squareHeight);
        } else {
            squareBitmap = bmp;
        }
    
        if (squareBitmap.getWidth() != diameter
                || squareBitmap.getHeight() != diameter) {
            scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter,
                    diameter, true);
    
        } else {
            scaledSrcBmp = squareBitmap;
        }
        Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(),
                scaledSrcBmp.getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(output);
        Paint paint = new Paint();
        Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(),
                scaledSrcBmp.getHeight());
        paint.setAntiAlias(true);
        paint.setFilterBitmap(true);
        paint.setDither(true);
        canvas.drawARGB(0, 0, 0, 0);
        canvas.drawCircle(scaledSrcBmp.getWidth() / 2,
                scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2,
                paint);
        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(scaledSrcBmp, rect, rect, paint);
        bmp = null;
        squareBitmap = null;
        scaledSrcBmp = null;
        return output;
    }
    View Code

    attrs.xml文件内容如下:

    <declare-styleable name="roundedimageview">
        <attr name="border_thickness" format="dimension" />
        <attr name="border_inside_color" format="color" />
        <attr name="border_outside_color" format="color"></attr>
    </declare-styleable>

    3、继承ViewGroup,实现简单滑动侧边栏菜单

    public class SlideMenuView extends ViewGroup {
    
        private Scroller scroller;//滑动器
        
        private final int MENU = 0;//显示菜单标识
        
        private final int MAIN = 1;//显示主页标识
        
        private int startx;//起始X位置
        
        private int currentScreen = MENU;//当前Screen
    
        public SlideMenuView(Context context, AttributeSet attrs) {
            super(context, attrs);
            scroller = new Scroller(context);
        }
    
        // 测量子view
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            View menu = getChildAt(0);
            menu.measure(menu.getLayoutParams().width, heightMeasureSpec);
            View main = getChildAt(1);
            main.measure(widthMeasureSpec, heightMeasureSpec);
        }
    
        // 将子view进行布局
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            View menu = getChildAt(0);
            menu.layout(-menu.getLayoutParams().width, t, 0, b);
            View main = getChildAt(1);
            main.layout(l, t, r, b);
        }
    
        // 触摸事件
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startx = (int) event.getX();// 记录手指按下时,点到屏幕左边的距离
                break;
            case MotionEvent.ACTION_MOVE:
                int movex = (int) event.getX();// 移动后,手指点到屏幕左边的距离
                int diffx = startx - movex;// 屏幕左边的偏移量
                int newscrollx = getScrollX() + diffx;// 偏移后
                if (newscrollx > 0) {
                    scrollTo(0, 0);// 如果屏幕左边超过了主界面左边,那么让屏幕左边与主界面重合
                } else if (newscrollx < -getChildAt(0).getWidth()) {
                    scrollTo(-getChildAt(0).getWidth(), 0);// 如果屏幕左边超过了侧边栏左边,那么让屏幕左边与侧边栏左边重合
                }
                scrollBy(diffx, 0);// 持续偏移
                startx = movex;
                break;
            case MotionEvent.ACTION_UP:
                int scrollx = getScrollX();// 屏幕左边距离主界面左边的距离,屏幕左边在主界面左边的左边,为负值
                if (scrollx > -getChildAt(0).getWidth() / 2) {
                    currentScreen = MAIN;// 拖动屏幕不到侧边栏的一半时,放手,显示主界面
                    switchScreen();
                } else if (scrollx < -getChildAt(0).getWidth() / 2) {
                    currentScreen = MENU;// 拖动屏幕超过了侧边栏的一般,放手,显示侧边栏
                    switchScreen();
                }
                break;
    
            default:
                break;
            }
            return true;
        }
    
        // 切换显示侧边栏和主界面
        private void switchScreen() {
            int dx = 0;
            // 获得屏幕左边距离主界面左边的距离
            int startX = getScrollX();
            if (currentScreen == MAIN) {
                // 目标是将屏幕左边与主界面左边重合
                dx = 0 - getScrollX();
            } else if (currentScreen == MENU) {
                // 目标是将屏幕左边与侧边栏的左边重合
                dx = -getChildAt(0).getWidth() - getScrollX();
            }
            scroller.startScroll(startX, 0, dx, 0, Math.abs(dx) * 5);
            invalidate();
        }
    
        // invalidate()的最终的调用方法就是computeScroll() 因此需要重写该方法
        @Override
        public void computeScroll() {
            if (scroller.computeScrollOffset()) {
                scrollTo(scroller.getCurrX(), 0);
                invalidate();
            }
        }
    
        // 判断当前显示的是不是侧边栏
        public boolean isMenuShow() {
            return currentScreen == MENU;
        }
    
        // 隐藏侧边栏
        public void hideMenu() {
            currentScreen = MAIN;
            switchScreen();
        }
    
        // 显示侧边栏
        public void showMenu() {
            currentScreen = MENU;
            switchScreen();
        }
    
    }

    4、自定义控件的使用

    <com.android.myself.view.SlideMenuView 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:myself="http://schemas.android.com/apk/res/com.android.myself"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
        
        <LinearLayout
            android:layout_width="120dp"
            android:layout_height="match_parent"
            android:background="#ffd5d1"
            android:orientation="vertical" >
            
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >
    
            <com.android.myself.view.RoundImageView
                android:layout_width="120dp"
                android:layout_height="120dp"
                android:scaleType="centerCrop"
                android:src="@drawable/jason"
                myself:border_inside_color="#fff7f2"
                myself:border_outside_color="#ffd5d1"
                myself:border_thickness="2dp" />
    
            <com.android.myself.view.CustomTextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                myself:backColor="#cccccc"
                myself:textColor="#FFFF00"
                myself:textContent="自定义"
                myself:textSize="16sp" />
        </LinearLayout>
    
    </com.android.myself.view.SlideMenuView>

    说明:xmlns:myself="http://schemas.android.com/apk/res/com.android.myself

    com.android.myself是工程文件的包名

    xmlns:myself是自定义的属性标签,可以随意写如xmlns:XXX,用的时候就是XXX:xxx

    截图一张如下:

    源码地址如下:http://files.cnblogs.com/files/pear-lemon/MySelf.zip

  • 相关阅读:
    JAVA07-Object类、Date类、Calendar类、System类、包装类、Collection、泛型、List、Set、数据结构、Collections
    大话数据结构03-线性表
    大话数据结构02-算法
    大话数据结构01-数据结构序论
    03-移动营销设计-H5设计方法
    02-移动营销设计-设计流程与规范技巧
    字典的定义和操作 (Python)
    列表的系列操作(python)
    列表操作之定义,切片(取元素)(Python)
    python的基础socket知识
  • 原文地址:https://www.cnblogs.com/pear-lemon/p/4611298.html
Copyright © 2011-2022 走看看