1d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen/*
2d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen * Copyright (C) 2015 The Android Open Source Project
3d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen *
4d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen * Licensed under the Apache License, Version 2.0 (the "License");
5d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen * you may not use this file except in compliance with the License.
6d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen * You may obtain a copy of the License at
7d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen *
8d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen *   http://www.apache.org/licenses/LICENSE-2.0
9d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen *
10d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen * Unless required by applicable law or agreed to in writing, software
11d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen * distributed under the License is distributed on an "AS IS" BASIS,
12d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen * See the License for the specific language governing permissions and
14d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen * limitations under the License.
15d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen */
16d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
17d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenpackage com.android.deskclock.widget;
18d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
19d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport android.annotation.SuppressLint;
20d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport android.content.Context;
21d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport android.content.res.TypedArray;
22d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport android.graphics.Canvas;
23d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport android.graphics.Color;
24d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport android.graphics.Paint;
25d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport android.util.AttributeSet;
26d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport android.util.Property;
27d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport android.view.Gravity;
28d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport android.view.View;
29d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
30d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenimport com.android.deskclock.R;
31d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
32d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen/**
33d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen * A {@link View} that draws primitive circles.
34d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen */
35d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassenpublic class CircleView extends View {
36d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
37d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
38d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * A Property wrapper around the fillColor functionality handled by the
39d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * {@link #setFillColor(int)} and {@link #getFillColor()} methods.
40d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
41d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public final static Property<CircleView, Integer> FILL_COLOR =
42d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            new Property<CircleView, Integer>(Integer.class, "fillColor") {
43d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        @Override
44d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        public Integer get(CircleView view) {
45d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            return view.getFillColor();
46d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
47d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
48d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        @Override
49d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        public void set(CircleView view, Integer value) {
50d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            view.setFillColor(value);
51d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
52d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    };
53d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
54d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
55d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * A Property wrapper around the radius functionality handled by the
56d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * {@link #setRadius(float)} and {@link #getRadius()} methods.
57d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
58d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public final static Property<CircleView, Float> RADIUS =
59d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            new Property<CircleView, Float>(Float.class, "radius") {
60d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        @Override
61d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        public Float get(CircleView view) {
62d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            return view.getRadius();
63d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
64d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
65d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        @Override
66d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        public void set(CircleView view, Float value) {
67d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            view.setRadius(value);
68d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
69d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    };
70d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
71d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
72d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * The {@link Paint} used to draw the circle.
73d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
74d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    private final Paint mCirclePaint = new Paint();
75d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
76d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    private int mGravity;
77d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    private float mCenterX;
78d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    private float mCenterY;
79d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    private float mRadius;
80d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
81d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public CircleView(Context context) {
82d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        this(context, null /* attrs */);
83d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
84d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
85d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public CircleView(Context context, AttributeSet attrs) {
86d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        this(context, attrs, 0 /* defStyleAttr */);
87d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
88d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
89d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
90d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        super(context, attrs, defStyleAttr);
91d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
92d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        final TypedArray a = context.obtainStyledAttributes(
93d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                attrs, R.styleable.CircleView, defStyleAttr, 0 /* defStyleRes */);
94d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
95d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        mGravity = a.getInt(R.styleable.CircleView_android_gravity, Gravity.NO_GRAVITY);
96d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        mCenterX = a.getDimension(R.styleable.CircleView_centerX, 0.0f);
97d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        mCenterY = a.getDimension(R.styleable.CircleView_centerY, 0.0f);
98d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        mRadius = a.getDimension(R.styleable.CircleView_radius, 0.0f);
99d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
100d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        mCirclePaint.setColor(a.getColor(R.styleable.CircleView_fillColor, Color.WHITE));
101d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
102d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        a.recycle();
103d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
104d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
105d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    @Override
106d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public void onRtlPropertiesChanged(int layoutDirection) {
107d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        super.onRtlPropertiesChanged(layoutDirection);
108d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
109d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        if (mGravity != Gravity.NO_GRAVITY) {
110d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            applyGravity(mGravity, layoutDirection);
111d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
112d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
113d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
114d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    @Override
115d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
116d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        super.onLayout(changed, left, top, right, bottom);
117d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
118d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        if (mGravity != Gravity.NO_GRAVITY) {
119d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            applyGravity(mGravity, getLayoutDirection());
120d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
121d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
122d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
123d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    @Override
124d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    protected void onDraw(Canvas canvas) {
125d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        super.onDraw(canvas);
126d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
127d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        // draw the circle, duh
128d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint);
129d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
130d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
131d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    @Override
132d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public boolean hasOverlappingRendering() {
133d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        // only if we have a background, which we shouldn't...
134d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return getBackground() != null && getBackground().getCurrent() != null;
135d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
136d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
137d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
138d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @return the current {@link Gravity} used to align/size the circle
139d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
140d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public final int getGravity() {
141d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return mGravity;
142d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
143d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
144d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
145d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * Describes how to align/size the circle relative to the view's bounds. Defaults to
146d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * {@link Gravity#NO_GRAVITY}.
147d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * <p/>
148d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * Note: using {@link #setCenterX(float)}, {@link #setCenterY(float)}, or
149d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * {@link #setRadius(float)} will automatically clear any conflicting gravity bits.
150d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     *
151d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @param gravity the {@link Gravity} flags to use
152d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @return this object, allowing calls to methods in this class to be chained
153d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @see R.styleable#CircleView_android_gravity
154d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
155d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public CircleView setGravity(int gravity) {
156d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        if (mGravity != gravity) {
157d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            mGravity = gravity;
158d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
159d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            if (gravity != Gravity.NO_GRAVITY && isLayoutDirectionResolved()) {
160d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                applyGravity(gravity, getLayoutDirection());
161d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            }
162d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
163d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return this;
164d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
165d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
166d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
167d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @return the ARGB color used to fill the circle
168d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
169d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public final int getFillColor() {
170d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return mCirclePaint.getColor();
171d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
172d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
173d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
174d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * Sets the ARGB color used to fill the circle and invalidates only the affected area.
175d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     *
176d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @param color the ARGB color to use
177d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @return this object, allowing calls to methods in this class to be chained
178d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @see R.styleable#CircleView_fillColor
179d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
180d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public CircleView setFillColor(int color) {
181d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        if (mCirclePaint.getColor() != color) {
182d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            mCirclePaint.setColor(color);
183d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
184d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            // invalidate the current area
185d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            invalidate(mCenterX, mCenterY, mRadius);
186d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
187d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return this;
188d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
189d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
190d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
191d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @return the x-coordinate of the center of the circle
192d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
193d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public final float getCenterX() {
194d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return mCenterX;
195d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
196d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
197d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
198d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * Sets the x-coordinate for the center of the circle and invalidates only the affected area.
199d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     *
200d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @param centerX the x-coordinate to use, relative to the view's bounds
201d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @return this object, allowing calls to methods in this class to be chained
202d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @see R.styleable#CircleView_centerX
203d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
204d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public CircleView setCenterX(float centerX) {
205d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        final float oldCenterX = mCenterX;
206d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        if (oldCenterX != centerX) {
207d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            mCenterX = centerX;
208d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
209d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            // invalidate the old/new areas
210d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            invalidate(oldCenterX, mCenterY, mRadius);
211d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            invalidate(centerX, mCenterY, mRadius);
212d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
213d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
214d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        // clear the horizontal gravity flags
215d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        mGravity &= ~Gravity.HORIZONTAL_GRAVITY_MASK;
216d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
217d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return this;
218d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
219d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
220d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
221d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @return the y-coordinate of the center of the circle
222d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
223d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public final float getCenterY() {
224d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return mCenterY;
225d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
226d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
227d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
228d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * Sets the y-coordinate for the center of the circle and invalidates only the affected area.
229d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     *
230d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @param centerY the y-coordinate to use, relative to the view's bounds
231d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @return this object, allowing calls to methods in this class to be chained
232d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @see R.styleable#CircleView_centerY
233d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
234d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public CircleView setCenterY(float centerY) {
235d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        final float oldCenterY = mCenterY;
236d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        if (oldCenterY != centerY) {
237d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            mCenterY = centerY;
238d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
239d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            // invalidate the old/new areas
240d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            invalidate(mCenterX, oldCenterY, mRadius);
241d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            invalidate(mCenterX, centerY, mRadius);
242d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
243d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
244d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        // clear the vertical gravity flags
245d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        mGravity &= ~Gravity.VERTICAL_GRAVITY_MASK;
246d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
247d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return this;
248d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
249d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
250d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
251d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @return the radius of the circle
252d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
253d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public final float getRadius() {
254d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return mRadius;
255d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
256d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
257d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
258d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * Sets the radius of the circle and invalidates only the affected area.
259d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     *
260d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @param radius the radius to use
261d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @return this object, allowing calls to methods in this class to be chained
262d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @see R.styleable#CircleView_radius
263d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
264d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    public CircleView setRadius(float radius) {
265d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        final float oldRadius = mRadius;
266d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        if (oldRadius != radius) {
267d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            mRadius = radius;
268d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
269d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            // invalidate the old/new areas
270d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            invalidate(mCenterX, mCenterY, oldRadius);
271d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            if (radius > oldRadius) {
272d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                invalidate(mCenterX, mCenterY, radius);
273d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            }
274d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
275d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
276d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        // clear the fill gravity flags
277d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        if ((mGravity & Gravity.FILL_HORIZONTAL) == Gravity.FILL_HORIZONTAL) {
278d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            mGravity &= ~Gravity.FILL_HORIZONTAL;
279d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
280d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        if ((mGravity & Gravity.FILL_VERTICAL) == Gravity.FILL_VERTICAL) {
281d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            mGravity &= ~Gravity.FILL_VERTICAL;
282d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
283d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
284d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        return this;
285d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
286d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
287d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
288d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * Invalidates the rectangular area that circumscribes the circle defined by {@code centerX},
289d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * {@code centerY}, and {@code radius}.
290d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
291d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    private void invalidate(float centerX, float centerY, float radius) {
292d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        invalidate((int) (centerX - radius - 0.5f), (int) (centerY - radius - 0.5f),
293d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                (int) (centerX + radius + 0.5f), (int) (centerY + radius + 0.5f));
294d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
295d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
296d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    /**
297d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * Applies the specified {@code gravity} and {@code layoutDirection}, adjusting the alignment
298d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * and size of the circle depending on the resolved {@link Gravity} flags. Also invalidates the
299d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * affected area if necessary.
300d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     *
301d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @param gravity the {@link Gravity} the {@link Gravity} flags to use
302d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     * @param layoutDirection the layout direction used to resolve the absolute gravity
303d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen     */
304d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    @SuppressLint("RtlHardcoded")
305d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    private void applyGravity(int gravity, int layoutDirection) {
306d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
307d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
308d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        final float oldRadius = mRadius;
309d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        final float oldCenterX = mCenterX;
310d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        final float oldCenterY = mCenterY;
311d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
312d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
313d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.LEFT:
314d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                mCenterX = 0.0f;
315d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                break;
316d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.CENTER_HORIZONTAL:
317d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.FILL_HORIZONTAL:
318d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                mCenterX = getWidth() / 2.0f;
319d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                break;
320d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.RIGHT:
321d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                mCenterX = getWidth();
322d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                break;
323d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
324d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
325d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
326d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.TOP:
327d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                mCenterY = 0.0f;
328d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                break;
329d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.CENTER_VERTICAL:
330d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.FILL_VERTICAL:
331d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                mCenterY = getHeight() / 2.0f;
332d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                break;
333d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.BOTTOM:
334d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                mCenterY = getHeight();
335d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                break;
336d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
337d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
338d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        switch (absoluteGravity & Gravity.FILL) {
339d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.FILL:
340d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                mRadius = Math.min(getWidth(), getHeight()) / 2.0f;
341d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                break;
342d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.FILL_HORIZONTAL:
343d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                mRadius = getWidth() / 2.0f;
344d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                break;
345d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            case Gravity.FILL_VERTICAL:
346d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                mRadius = getHeight() / 2.0f;
347d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen                break;
348d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
349d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen
350d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        if (oldCenterX != mCenterX || oldCenterY != mCenterY || oldRadius != mRadius) {
351d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            invalidate(oldCenterX, oldCenterY, oldRadius);
352d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen            invalidate(mCenterX, mCenterY, mRadius);
353d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen        }
354d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen    }
355d904cd4d97b5f10c0f65642c85d375ffc9b2ec3cJustin Klaassen}
356