Создаем файл res/values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_radius" format="integer"/>
</declare-styleable>
</resources>
CircleView - название класса на Java, который будет управлять функциональностью элемента.
circle_radius - атрибут элемента, значение которого можно будет задать в разметке при создании элемента.
integer - тип значения, принимаемого атрибутом.
Создаем класс для нашего элемента (CircleView.java), содержащий конструктор с двумя параметрами и унаследованный от класса android.view.View:
public class CircleView extends View {
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
Получить значение атрибута можно так:
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircleView);
radius = typedArray.getInt(R.styleable.CircleView_circle_radius, 0);
typedArray.recycle();
Чтобы радиус был доступен в методе отрисовки, опишем поле класса и запишем это значение в него:
public class CircleView extends View {
private int radius;
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircleView);
radius = typedArray.getInt(R.styleable.CircleView_circle_radius, 0);
typedArray.recycle();
}
}
Теперь добавим отрисовку компонента. Для этого в классе CircleView необходимо переопределить метод onDraw:
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawCircle(30, 30, radius);
}
Весь файл (CircleView.java):
public class CircleView extends View {
private int radius;
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircleView);
radius = typedArray.getInt(R.styleable.CircleView_circle_radius, 0);
typedArray.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawCircle(30, 30, radius, paint);
}
}
Осталось добавить компонент в разметку приложения, чтобы протестировать его работу (activity_main.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.example.myapplication.CircleView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:circle_radius="100">
</com.example.myapplication.CircleView>
</LinearLayout>
Далее показан пример View для отображения гистограммы.
Файл res/values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HistogramView">
<attr name="hist_data" format="string"/>
<attr name="hist_text_color" format="color"/>
<attr name="hist_cols_color" format="color"/>
<attr name="hist_background_color" format="color"/>
<attr name="hist_line_color" format="color"/>
</declare-styleable>
</resources>
Элемент сможет принимать 5 параметров:
hist_data - данные для столбиков диаграммы;
text_color - цвет текста на гистограмме;
cols_color - цвет столбцов гистограммы;
background_color - цвет фона гистограммы;
line_color - цвет линии (оси Ох).
Данные буду приниматься в строковом формате. В строку будут записаны данные в виде JSON, но парсить буду их вручную.
Класс HistogramView :
public class HistogramView extends View {
private ArrayList<String> keys;
private ArrayList<Integer> values;
private Paint columnPaint, textPaint, linePaint, backgroundPaint;
public HistogramView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.HistogramView);
String data = typedArray.getString(R.styleable.HistogramView_hist_data);
String columnColor = typedArray.getString(R.styleable.HistogramView_hist_cols_color);
String textColor = typedArray.getString(R.styleable.HistogramView_hist_text_color);
String backgroundColor = typedArray.getString(R.styleable.HistogramView_hist_background_color);
String lineColor = typedArray.getString(R.styleable.HistogramView_hist_line_color);
typedArray.recycle();
setData(data);
columnPaint = new Paint();
if(columnColor == null){
columnPaint.setColor(Color.parseColor("#000000"));
}else{
columnPaint.setColor(Color.parseColor(columnColor));
}
columnPaint.setStyle(Paint.Style.FILL);
textPaint = new Paint();
if(columnColor == null){
textPaint.setColor(Color.parseColor("#000000"));
}else{
textPaint.setColor(Color.parseColor(textColor));
}
textPaint.setStyle(Paint.Style.FILL);
backgroundPaint = new Paint();
if(columnColor == null){
backgroundPaint.setColor(Color.parseColor("#FFFFFF"));
}else{
backgroundPaint.setColor(Color.parseColor(backgroundColor));
}
backgroundPaint.setStyle(Paint.Style.FILL);
linePaint = new Paint();
if(columnColor == null){
linePaint.setColor(Color.parseColor("#000000"));
}else{
linePaint.setColor(Color.parseColor(lineColor));
}
linePaint.setStrokeWidth(5);
}
public void setData(String data){
if (data != null){
data = data.replace('{', ' ');
data = data.replace('}', ' ');
String [] s = data.split(",");
keys = new ArrayList<>();
values = new ArrayList<>();
for (String value : s) {
String key = value.substring(0, value.lastIndexOf(':'));
String val = value.substring(value.lastIndexOf(':') + 1);
key = key.replace('"', ' ');
key = key.trim();
val = val.trim();
//Log.i("DATA", key + " " + val);
keys.add(key);
values.add(Integer.valueOf(val));
}
}
invalidate(); // метод для перерисовки
}
@Override
protected void onDraw(Canvas canvas) {
float H = getHeight();
float hAll = 3 * H / 4;
float h = 9 * hAll / 10;
float paddingY = H - hAll, paddingTop = paddingY / 2, paddingBottom = paddingY / 2;
float W = getWidth();
float w = 9 * W / 10;
float paddingX = W - w, paddingLeft = paddingX / 2, paddingRight = paddingX / 2;
float dataCount = keys.size() * 2 + 1;
float stepX = w / dataCount;
float textSize = Math.min(stepX, paddingBottom);
textPaint.setTextSize(textSize);
canvas.drawRect(0, 0, W, H, backgroundPaint);
int mx = 0;
for (Integer val: values) mx = Math.max(val, mx);
ArrayList<Float> percents = new ArrayList<>();
for (Integer val: values) percents.add(Float.valueOf(1.0f * val / mx * h));
float x = stepX + paddingLeft;
for(int i = 0; i<percents.size(); i++){
canvas.drawRect(x - stepX / 4, paddingTop + h, x + stepX + stepX / 4, paddingTop + h - percents.get(i), columnPaint);
canvas.drawText(keys.get(i), x + stepX / 8, paddingTop + h + textSize, textPaint);
x += 2 * stepX;
}
canvas.drawLine(paddingLeft, paddingTop + h, paddingLeft + w, paddingTop + h, linePaint);
}
}
invalidate() - метод для перерисовки экрана при получении новых данных с помощью функции setData
Разметка (activity_main.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.example.myapplication.HistogramView
android:layout_width="match_parent"
android:id="@+id/hist"
app:hist_data='{"1": 34, "2": 16, "3": 13, "4": 15, "5": 17, "6": 35, "7": 21, "8": 7, "9": 42, "10": 44}'
android:layout_height="300dp">
</com.example.myapplication.HistogramView>
</LinearLayout>
Данные в JSON:
{
"1": 34,
"2": 16,
"3": 13,
"4": 15,
"5": 17,
"6": 35,
"7": 21,
"8": 7,
"9": 42,
"10": 44
}
Данные для отображения можно было задать в разметке, а можно так (MainActivity.java):
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HistogramView histogramView = findViewById(R.id.hist);
String s = "{\"1\": 34, \"2\": 16, \"3\": 13, \"4\": 15, \"5\": 17, \"6\": 35, \"7\": 21, \"8\": 7, \"9\": 42, \"10\": 44}";
histogramView.setData(s);
}
}