Android 自定义控件 - 圈圈

自定义控件 - 圈圈

Android L; Android Studio

http://www.cnblogs.com/rustfisher/p/5132212.html

效果:能够自定义圆圈半径和位置;设定点击效果;改变背景颜色

下面是demo图
点击前:

1
1
点击后:

自定义控件一般要继承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,效果更好看

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,050评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,090评论 4 62
  • 没有故事就过不下去的人,大概像是吃饭不能不放盐一样的感觉吧。与此相对应的,是把自己的生活果成故事或者自己本身自带主...
    兰岚Lillian阅读 224评论 0 0
  • 1、 我小时候很胖,长大点了也是。 初中暗恋过班上的一个男生,偷偷往他书桌里塞过情书。我写字很漂亮,他大概是以为年...
    许北君阅读 614评论 4 8
  • 我自已早上5点从醒来,去儿子盖被子,然后又睡了一会,老公7点去段练,我也就起来,老是静不下来做事,跑了一会步,看了...
    牛牛_dc12阅读 125评论 0 0