Android Color Picker Tutorial
User Interface (UI) design is an important part of an app. Color (or colour in British English) is an important part of a UI. A color picker can be used to customize or edit an app theme or UI. Plus they can be used for artistic apps. This tutorial provides an Android Color Picker example app, but not just showing one way. Two ways are provided in this article and lots of color pickers are available around the web.
(This Android color picker tutorial assumes that Android Studio is installed, a basic App can be created and run, and the code in this article can be correctly copied into Android Studio. The example code can be changed to meet your own requirements. When entering code in Studio add import statements when prompted by pressing Alt-Enter.)
How is an Android Color Defined?
What is a color in the Android Operating System (OS)? A color in Android is simply a number, a 32-bit number. 32-bits fits into a Java int. Therefore a standard color in Android is stored in an int. The 32-bits of the color int are divided into four lots of eight bits (four bytes). The four bytes are the Alpha (transparancy), Red, Green and Blue parts of a color. A color is therefore often simply referred to as an RGB or ARGB value. Each byte can hold 256 values (0-255). Therefore, the total number of possible colors are 256x256x256=16777216. Over 16 million colours and at 256 transparency levels. To define a color just assign a value to an integer, here is an example:
//---------A-R-G-B-
int lime=0xff00ff00;
findViewById(R.id.textView).setBackgroundColor(lime);
A Simple Android Color Picker Example
Because an Android color is just a combination of four bytes a simple color picker can be made with four Seekbars dropped on to a View. Then take the value from the Seekbars and convert them into a color using the Android Color class.
For an example on using the Seekbar see the Tek Eye article SeekBar Android Example Code and Tutorial. Here is the layout with four Seekbars used to set an Android color (as shown above):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
tools:context="com.example.colorpicker.MainActivity">
<TextView
android:id="@+id/labelA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A"
android:textAppearance="?android:attr/textAppearanceMedium" />
<SeekBar
android:id="@+id/seekA"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/labelA"
android:layout_toRightOf="@id/labelA"
android:max="255"
android:progress="255" />
<TextView
android:id="@+id/labelR"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/labelA"
android:text="R"
android:textAppearance="?android:attr/textAppearanceMedium" />
<SeekBar
android:id="@+id/seekR"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/labelR"
android:layout_below="@+id/seekA"
android:layout_toRightOf="@+id/labelR"
android:max="255"
android:progress="255" />
<TextView
android:id="@+id/labelG"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/labelR"
android:text="G"
android:textAppearance="?android:attr/textAppearanceMedium" />
<SeekBar
android:id="@+id/seekG"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/labelG"
android:layout_below="@+id/seekR"
android:layout_toRightOf="@+id/labelG"
android:max="255"
android:progress="255" />
<TextView
android:id="@+id/labelB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/labelG"
android:text="B"
android:textAppearance="?android:attr/textAppearanceMedium" />
<SeekBar
android:id="@+id/seekB"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/labelB"
android:layout_below="@+id/seekG"
android:layout_toRightOf="@+id/labelB"
android:max="255"
android:progress="255" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/labelB"
android:background="@android:color/darker_gray"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/labelB"
android:layout_centerHorizontal="true"
android:layout_margin="24dp"
android:background="@android:color/white"
android:padding="24dp"
android:text="0xffffffff"
android:textAlignment="center"
android:textColor="@android:color/black"
android:textSize="36dp" />
</LinearLayout>
</RelativeLayout>
Here is the Java Activity source code (MainActivity.java) used for the above layout:
package com.example.colorpicker;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener {
//Reference the seek bars
SeekBar SeekA;
SeekBar SeekR;
SeekBar SeekG;
SeekBar SeekB;
//Reference the TextView
TextView ShowColor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Get a reference to the seekbars
SeekA=(SeekBar)findViewById(R.id.seekA);
SeekR=(SeekBar)findViewById(R.id.seekR);
SeekG=(SeekBar)findViewById(R.id.seekG);
SeekB=(SeekBar)findViewById(R.id.seekB);
//Reference the TextView
ShowColor=(TextView)findViewById(R.id.textView);
//This activity implements SeekBar OnSeekBarChangeListener
SeekA.setOnSeekBarChangeListener(this);
SeekR.setOnSeekBarChangeListener(this);
SeekG.setOnSeekBarChangeListener(this);
SeekB.setOnSeekBarChangeListener(this);
}
//Satisfy the implements
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
//get current ARGB values
int A=SeekA.getProgress();
int R=SeekR.getProgress();
int G=SeekG.getProgress();
int B=SeekB.getProgress();
//Reference the value changing
int id=seekBar.getId();
//Get the chnaged value
if(id == com.example.colorpicker.R.id.seekA)
A=progress;
else if(id == com.example.colorpicker.R.id.seekR)
R=progress;
else if(id == com.example.colorpicker.R.id.seekA)
G=progress;
else if(id == com.example.colorpicker.R.id.seekA)
B=progress;
//Build and show the new color
ShowColor.setBackgroundColor(Color.argb(A,R,G,B));
//show the color value
ShowColor.setText("0x"+String.format("%02x", A)+String.format("%02x", R)
+String.format("%02x", G)+String.format("%02x", B));
//some math so text shows (needs improvement for greys)
ShowColor.setTextColor(Color.argb(0xff,255-R,255-G,255-B));
}
public void onStartTrackingTouch(SeekBar seekBar) {
//Only required due to implements
}
public void onStopTrackingTouch(SeekBar seekBar) {
//Only required due to implements
}
}
An Android Color Picker Dialog
The Android Software Development Kit (SDK) used to ship with many demo apps. One of the most useful was the API Demos project. The API Demos covers many core Android classes. It features graphics examples and one of those has a color picker dialog.
To implement it copy ColorPickerDialog.java from the graphics folder of the API Demos project into the same folder as the main Java file (here MainActivity.java). Change the package namespace to match the app's (here package com.example.colorpicker;
). Add a Button to the top of the layout and set the first TextView to be below it:
...
<Button
android:id="@+id/buttonA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Picker 1"
android:onClick="Picker1Click" />
<TextView
android:id="@+id/labelA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/buttonA"
android:text="A"
android:textAppearance="?android:attr/textAppearanceMedium" />
...
Add code to handle the button and invoke the ColorPickerDialog:
//Handle button
public void Picker1Click(View arg0) {
//no direct way to get background color as it could be a drawable
if (ShowColor.getBackground() instanceof ColorDrawable) {
ColorDrawable cd = (ColorDrawable) ShowColor.getBackground();
int colorCode = cd.getColor();
//pick a color (changed in the UpdateColor listener)
new ColorPickerDialog(MainActivity.this, new UpdateColor(), colorCode).show();
}
}
And code to handle the color picker dialog return:
public class UpdateColor implements ColorPickerDialog.OnColorChangedListener {
public void colorChanged(int color) {
//ShowColor.setBackgroundColor(color);
//show the color value
ShowColor.setText("0x"+String.format("%08x", color));
SeekA.setProgress(Color.alpha(color));
SeekR.setProgress(Color.red(color));
SeekG.setProgress(Color.green(color));
SeekB.setProgress(Color.blue(color));
}
}
The ColorPickerDialog is showing its age as it has hard coded values for when screens were much smaller. Here some simple scaling was added based on density pixels (dp) and the values tweeked. It'll need further changes to correctly handle different screen sizes if it goes into production code. Here is the code for the ColorPickerDialog.java used in the example:
package com.example.colorpicker;
import android.os.Bundle;
import android.app.Dialog;
import android.content.Context;
import android.graphics.*;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
public class ColorPickerDialog extends Dialog {
public interface OnColorChangedListener {
void colorChanged(int color);
}
private OnColorChangedListener mListener;
private int mInitialColor;
private static class ColorPickerView extends View {
private Paint mPaint;
private Paint mCenterPaint;
private final int[] mColors;
private OnColorChangedListener mListener;
ColorPickerView(Context c, OnColorChangedListener l, int color) {
super(c);
mListener = l;
mColors = new int[] {
0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00,
0xFFFFFF00, 0xFFFF0000
};
Shader s = new SweepGradient(0, 0, mColors, null);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setShader(s);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dpToPx(64));
mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCenterPaint.setColor(color);
mCenterPaint.setStrokeWidth(5);
}
private boolean mTrackingCenter;
private boolean mHighlightCenter;
@Override
protected void onDraw(Canvas canvas) {
float r = CENTER_X - mPaint.getStrokeWidth()*0.5f;
canvas.translate(CENTER_X, CENTER_X);
canvas.drawOval(new RectF(-r, -r, r, r), mPaint);
canvas.drawCircle(0, 0, CENTER_RADIUS, mCenterPaint);
if (mTrackingCenter) {
int c = mCenterPaint.getColor();
mCenterPaint.setStyle(Paint.Style.STROKE);
if (mHighlightCenter) {
mCenterPaint.setAlpha(0xFF);
} else {
mCenterPaint.setAlpha(0x80);
}
canvas.drawCircle(0, 0,
CENTER_RADIUS + mCenterPaint.getStrokeWidth(),
mCenterPaint);
mCenterPaint.setStyle(Paint.Style.FILL);
mCenterPaint.setColor(c);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(CENTER_X*2, CENTER_Y*2);
}
private int CENTER_X = dpToPx(132);
private int CENTER_Y = dpToPx(132);
private int CENTER_RADIUS = dpToPx(40);
private int floatToByte(float x) {
int n = java.lang.Math.round(x);
return n;
}
private int pinToByte(int n) {
if (n < 0) {
n = 0;
} else if (n > 255) {
n = 255;
}
return n;
}
private int dpToPx(int dp) {
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
private int ave(int s, int d, float p) {
return s + java.lang.Math.round(p * (d - s));
}
private int interpColor(int colors[], float unit) {
if (unit <= 0) {
return colors[0];
}
if (unit >= 1) {
return colors[colors.length - 1];
}
float p = unit * (colors.length - 1);
int i = (int)p;
p -= i;
// now p is just the fractional part [0...1) and i is the index
int c0 = colors[i];
int c1 = colors[i+1];
int a = ave(Color.alpha(c0), Color.alpha(c1), p);
int r = ave(Color.red(c0), Color.red(c1), p);
int g = ave(Color.green(c0), Color.green(c1), p);
int b = ave(Color.blue(c0), Color.blue(c1), p);
return Color.argb(a, r, g, b);
}
private int rotateColor(int color, float rad) {
float deg = rad * 180 / 3.1415927f;
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
ColorMatrix cm = new ColorMatrix();
ColorMatrix tmp = new ColorMatrix();
cm.setRGB2YUV();
tmp.setRotate(0, deg);
cm.postConcat(tmp);
tmp.setYUV2RGB();
cm.postConcat(tmp);
final float[] a = cm.getArray();
int ir = floatToByte(a[0] * r + a[1] * g + a[2] * b);
int ig = floatToByte(a[5] * r + a[6] * g + a[7] * b);
int ib = floatToByte(a[10] * r + a[11] * g + a[12] * b);
return Color.argb(Color.alpha(color), pinToByte(ir),
pinToByte(ig), pinToByte(ib));
}
private static final float PI = 3.1415926f;
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX() - CENTER_X;
float y = event.getY() - CENTER_Y;
boolean inCenter = java.lang.Math.hypot(x, y) <= CENTER_RADIUS;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mTrackingCenter = inCenter;
if (inCenter) {
mHighlightCenter = true;
invalidate();
break;
}
case MotionEvent.ACTION_MOVE:
if (mTrackingCenter) {
if (mHighlightCenter != inCenter) {
mHighlightCenter = inCenter;
invalidate();
}
} else {
float angle = (float)java.lang.Math.atan2(y, x);
// need to turn angle [-PI ... PI] into unit [0....1]
float unit = angle/(2*PI);
if (unit < 0) {
unit += 1;
}
mCenterPaint.setColor(interpColor(mColors, unit));
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if (mTrackingCenter) {
if (inCenter) {
mListener.colorChanged(mCenterPaint.getColor());
}
mTrackingCenter = false; // so we draw w/o halo
invalidate();
}
break;
}
return true;
}
}
public ColorPickerDialog(Context context,
OnColorChangedListener listener,
int initialColor) {
super(context);
mListener = listener;
mInitialColor = initialColor;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OnColorChangedListener l = new OnColorChangedListener() {
public void colorChanged(int color) {
mListener.colorChanged(color);
dismiss();
}
};
setContentView(new ColorPickerView(getContext(), l, mInitialColor));
setTitle("Pick Color, Click the Dot");
}
}
Lots of Color Pickers
There are lots of free color pickers available for Android. Many with more features than the ones shown here. Browse for an Android color picker on GitHub to find one suitable for your project, if the simple ones shown here are not up to the job.
Good UI Design
Color is only one small part of UI design. A good UI makes an App more pleasurable to use and engaging.
You can learn all about UI design at the Interaction Design Foundation. Head over to their UI design resource, it includes tips on how to make a great UI, at: https://www.interaction-design.org/literature/topics/ui-design
See Also
- Download the final code for this example, available in color-picker.zip
- See the SeekBar Android Example Code and Tutorial article for a SeekBar example.
- See the Android Example Projects page for more Android Studio sample projects with source code.
- For a full list of all the articles in Tek Eye see the full site alphabetical Index.
Author:Daniel S. Fowler Published: Updated: