天天看點

Android - 自定義控件之圓形控件

自定義控件 - 圈圈

Android L; Android Studio

效果:能夠自定義圓圈半徑和位置;設定點選效果;改變背景顔色

下面是demo圖

點選前: 點選後:

自定義控件一般要繼承View;寫出構造方法,并設定屬性;複寫

onDraw

方法

并在xml中配置一下

例子:

OptionCircle.java

CirclesActivity.java

activity_circle_choose.xml

這個例子沒有使用

attrs.xml

控件 OptionCircle

這裡繼承的是ImageView;設定了多個屬性,有半徑,圓心位置,背景顔色和字型顔色等等

針對這些屬性,開放set方法;友善設定屬性;可以改變這些屬性來做出一些動畫效果

構造方法中預設幾個屬性,設定畫筆,背景顔色和圓圈的半徑

onDraw

方法中開始繪制控件;先畫圓形,在圓形中心畫上文字;文字中心定位需要特别計算一下

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class OptionCircle extends ImageView {

    private final Paint paint;
    private final Context context;
    boolean clicked = false;// 是否被點選
    boolean addBackground = false;
    int radius = -1;      // 半徑值初始化為-1
    int centerOffsetX = 0;// 圓圈原點的偏移量x
    int centerOffsetY = 0;// 偏移量y
    int colorCircle;      // 圓圈顔色
    int colorBackground;  // 背景填充顔色
    int colorText;        // 文字顔色
    String textCircle = "";

    public OptionCircle(Context context) {
        this(context, null);
    }

    public OptionCircle(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        this.paint = new Paint();
        this.paint.setAntiAlias(true);
        this.paint.setStyle(Paint.Style.STROKE);
        colorCircle = Color.argb(205, 245, 2, 51);// 預設顔色
        colorText = colorCircle;      // 字型顔色預設與圈圈顔色保持一緻
        colorBackground = colorCircle;// 設定預設參數
    }
    // 屬性設定方法
    public void setRadius(int r) {
        this.radius = r;
    }

    public void setCenterOffset(int x, int y) {
        this.centerOffsetX = x;
        this.centerOffsetY = y;
    }

    public void setColorCircle(int c) {
        this.colorCircle = c;
    }

    public void setColorText(int c) {
        this.colorText = c;
    }

    public void setColorBackground(int c) {
        this.colorBackground = c;
    }

    public void setText(String s) {
        this.textCircle = s;
    }

    public void setClicked(boolean clicked) {
        this.clicked = clicked;
    }

    public void setAddBackground(boolean add) {
        this.addBackground = add;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int center = getWidth() / 2;// 目前寬度的一半
        int innerCircle = 86;       // 預設半徑為86
        if (radius > 0) {
            innerCircle = dip2px(context, radius); // 如果沒有另外設定半徑,取半徑86
        }

        Drawable drawable = getDrawable();
        if (addBackground) {
        } else {
            // 畫圈圈;被點選後會變成實心的圈圈,預設是空心的
            this.paint.setStyle(clicked ? Paint.Style.FILL : Paint.Style.STROKE);
            this.paint.setColor(clicked ? colorBackground : colorCircle);
            this.paint.setStrokeWidth(1.5f);
            canvas.drawCircle(center + centerOffsetX, center + centerOffsetY,
                    innerCircle, this.paint);// 畫圓圈時帶上偏移量
        }

        // 繪制文字
        this.paint.setStyle(Paint.Style.FILL);
        this.paint.setStrokeWidth(1);
        this.paint.setTextSize(22);
        this.paint.setTypeface(Typeface.MONOSPACE);// 設定一系列文字屬性
        this.paint.setColor(clicked ? Color.WHITE : colorText);
        this.paint.setTextAlign(Paint.Align.CENTER);// 文字水準居中
        Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
        canvas.drawText(textCircle, center + centerOffsetX,
                center + centerOffsetY - (fontMetrics.top + fontMetrics.bottom) / 2, this.paint);// 設定文字豎直方向居中
        super.onDraw(canvas);
    }

    /**
     * convert dp to px
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}           

配置 activity_circle_choose.xml

控件檔案定義完畢,在

activity_circle_choose.xml

中配置一下

定義4個圈圈;center_circle定位在中心;circle_0是紅色的;circle_1是綠色的;circle_2是洋紅色的

<?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">

    <TextView
        android:id="@+id/top_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="5dp"
        android:text="@string/circles_top_title"
        android:textSize="26sp" />

    <com.rust.aboutview.view.OptionCircle
        android:id="@+id/center_circle"
        android:layout_width="140dp"
        android:layout_height="140dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />

    <com.rust.aboutview.view.OptionCircle
        android:id="@+id/circle_0"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginStart="130dp"
        android:layout_marginTop="53dp" />

    <com.rust.aboutview.view.OptionCircle
        android:id="@+id/circle_1"
        android:layout_width="210dp"
        android:layout_height="210dp"
        android:layout_below="@+id/circle_0"
        android:layout_toEndOf="@+id/center_circle" />

    <com.rust.aboutview.view.OptionCircle
        android:id="@+id/circle_2"
        android:layout_width="210dp"
        android:layout_height="210dp"
        android:layout_below="@id/center_circle" />

</RelativeLayout>           

在 CirclesActivity.java 中使用圈圈

圈圈類

OptionCircle.java

已經開放了設定屬性的方法,我們可以利用這些方法來調整圈圈的樣式,比如半徑,顔色,圓心偏移量

center_circle固定在螢幕中間不動

circle_0仿造一個放大縮小的效果,改變半徑值即可實作

circle_1仿造一個浮動的效果,改變圓心偏移量來實作

circle_2仿造抖動效果,也是改變圓心偏移量

這些圈圈都可以自定義背景顔色

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;

import com.rust.aboutview.view.OptionCircle;

public class CirclesActivity extends Activity {

    public static final String TAG = "CirclesActivity";
    public static final int circle0_r = 88;

    private static final int SLEEPING_PERIOD = 100; // 重新整理UI間隔時間
    private static final int UPDATE_ALL_CIRCLE = 99;
    int circleCenter_r;
    int circle1_r;
    boolean circle0Clicked = false;
    boolean circle1Clicked = false;

    OptionCircle centerCircle;
    OptionCircle circle0;
    OptionCircle circle1;
    OptionCircle circle2;

    CircleHandler handler = new CircleHandler(this);

    /**
     * Handler : 用于更新UI
     */
    static class CircleHandler extends Handler {
        CirclesActivity activity;
        boolean zoomDir = true;
        boolean circle2Shaking = false;
        int r = circle0_r;
        int moveDir = 0;  // 浮動方向
        int circle1_x = 0;// 偏移量的值
        int circle1_y = 0;
        int circle2_x = 0;
        int circle2ShakeTime = 0;
        int circle2Offsets[] = {10, 15, -6, 12, 0};// 抖動偏移量坐标

        CircleHandler(CirclesActivity a) {
            activity = a;
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_ALL_CIRCLE: {
                    if (zoomDir) {// 用簡單的辦法實作半徑變化
                        r++;
                        if (r >= 99) zoomDir = false;
                    } else {
                        r--;
                        if (r <= circle0_r) zoomDir = true;
                    }
                    activity.circle0.invalidate();
                    activity.circle0.setRadius(r);
                    calOffsetX();// 計算圓心偏移量
                    activity.circle1.invalidate();
                    activity.circle1.setCenterOffset(circle1_x, circle1_y);

                    if (circle2Shaking) {
                        if (circle2ShakeTime < circle2Offsets.length - 1) {
                            circle2ShakeTime++;
                        } else {
                            circle2Shaking = false;
                            circle2ShakeTime = 0;
                        }
                        activity.circle2.invalidate();
                        activity.circle2.setCenterOffset(circle2Offsets[circle2ShakeTime], 0);
                    }
                }
            }
        }
        // 計算circle1圓心偏移量;共有4個浮動方向
        private void calOffsetX() {
            if (moveDir == 0) {
                circle1_x--;
                circle1_y++;
                if (circle1_x <= -6) moveDir = 1;
            }
            if (moveDir == 1) {
                circle1_x++;
                circle1_y++;
                if (circle1_x >= 0) moveDir = 2;
            }
            if (moveDir == 2) {
                circle1_x++;
                circle1_y--;
                if (circle1_x >= 6) moveDir = 3;
            }
            if (moveDir == 3) {
                circle1_x--;
                circle1_y--;
                if (circle1_x <= 0) moveDir = 0;
            }
        }
    }

    class UpdateCircles implements Runnable {

        @Override
        public void run() {
            while (true) {// 配合Handler,循環重新整理UI
                Message message = new Message();
                message.what = UPDATE_ALL_CIRCLE;
                handler.sendEmptyMessage(message.what);
                try {
                    Thread.sleep(SLEEPING_PERIOD); // 暫停
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_choose);
        centerCircle = (OptionCircle) findViewById(R.id.center_circle);
        circle0 = (OptionCircle) findViewById(R.id.circle_0);
        circle1 = (OptionCircle) findViewById(R.id.circle_1);
        circle2 = (OptionCircle) findViewById(R.id.circle_2);

        circleCenter_r = 38;
        circle1_r = 45;
        // 設定圈圈的屬性
        centerCircle.setRadius(circleCenter_r);
        centerCircle.setColorText(Color.BLUE);
        centerCircle.setColorCircle(Color.BLUE);
        centerCircle.setText("點選圈圈");

        circle0.setColorText(Color.RED);
        circle0.setRadius(circle0_r);
        circle0.setText("RED");

        circle1.setColorCircle(Color.GREEN);
        circle1.setColorText(Color.GREEN);
        circle1.setText("Green");
        circle1.setRadius(circle1_r);

        circle2.setColorCircle(getResources().getColor(R.color.colorMagenta));
        circle2.setColorText(getResources().getColor(R.color.colorMagenta));
        circle2.setText("Frozen!");

        // 設定點選事件,可在這裡改變控件的屬性
        circle0.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                circle0Clicked = !circle0Clicked;  // 每次點選都取反
                circle0.setClicked(circle0Clicked);
            }
        });

        circle1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                circle1Clicked = !circle1Clicked;
                circle1.setColorBackground(Color.GREEN);
                circle1.setClicked(circle1Clicked);
            }
        });

        circle2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handler.circle2Shaking = true;// 顫抖吧!
            }
        });

        Thread t = new Thread(new UpdateCircles());
        t.start();// 開啟子線程
    }
}           

至此,圈圈demo結束。通過簡單的計算,模拟出浮動,抖動,縮放的效果

以上的代碼,複制粘貼進工程裡就能使用。圓心移動的軌迹,用三角函數來計算會更好

這裡繼承的是ImageView,應該有辦法在圈内動态添加背景Bitmap,效果更好看

繼續閱讀