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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM