This tutorial demonstrates how to allow users to draw with opacity values. While this post builds on related tutorials published on Mobiletuts+, you can dive straight into this lesson without completing the prior posts. Read on!
The Android platform provides the resources to create drawing functionality using touchscreen interaction. In a prior Mobiletuts+ series on Creating a Drawing App, we worked through the essential features of drawing interaction in Android, including selecting from a color palette and choosing brush sizes. In this tutorial, we will focus on how to enhance a drawing application by adding opacity into app drawing functions. You can complete this tutorial without having completed the related posts, but we will reference some of the prior material throughout.
Final Preview
Here is a preview of the opacity drawing functionality:
The source code download includes the standalone app we build in this tutorial as well as an enhanced version of the app we built during the drawing series. Here is a preview of it with the additional functionality:
1. Start Your Application
Step 1
As with the pattern drawing tutorial, we will be glossing over some of the details we explored in the previous drawing series so that we can focus on the opacity drawing functionality. In addition to using opacity levels, we will also be adding a control to the user interface so that the user can select their own opacity level for drawing.
If you completed the drawing app series, you can jump straight to part 2 step 1 now. If you are creating a new app for this tutorial, start a new Android project in Eclipse now. Choose 14 as your minimum API level and select other settings of your choice. When creating the app, you can let Eclipse create a blank Activity and layout.
Add a new class to your app, naming it “DrawingView”. Start with the same class content we used for the pattern drawing tutorial:
import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class DrawingView extends View { //drawing path private Path drawPath; //drawing and canvas paint private Paint drawPaint, canvasPaint; //initial color private int paintColor = 0xFFFF0000; //canvas private Canvas drawCanvas; //canvas bitmap private Bitmap canvasBitmap; //constructor public DrawingView(Context context, AttributeSet attrs){ super(context, attrs); setupDrawing(); } //prepare drawing private void setupDrawing(){ drawPath = new Path(); drawPaint = new Paint(); drawPaint.setColor(paintColor); drawPaint.setAntiAlias(true); drawPaint.setStrokeWidth(50); drawPaint.setStyle(Paint.Style.STROKE); drawPaint.setStrokeJoin(Paint.Join.ROUND); drawPaint.setStrokeCap(Paint.Cap.ROUND); canvasPaint = new Paint(Paint.DITHER_FLAG); } //view assigned size @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); drawCanvas = new Canvas(canvasBitmap); } //draw view @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint); canvas.drawPath(drawPath, drawPaint); } //respond to touch interaction @Override public boolean onTouchEvent(MotionEvent event) { float touchX = event.getX(); float touchY = event.getY(); //respond to down, move and up events switch (event.getAction()) { case MotionEvent.ACTION_DOWN: drawPath.moveTo(touchX, touchY); break; case MotionEvent.ACTION_MOVE: drawPath.lineTo(touchX, touchY); break; case MotionEvent.ACTION_UP: drawPath.lineTo(touchX, touchY); drawCanvas.drawPath(drawPath, drawPaint); drawPath.reset(); break; default: return false; } //redraw invalidate(); return true; } }
The content of the class contains standard drawing app functionality – for more information on the details see the drawing app series.
Step 2
Let’s now include the new View in the app user interface along with the other controls. Open your layout file. Replace the content with the following layout:
<LinearLayout 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:background="#FFCCCCCC" android:orientation="vertical" tools:context=".MainActivity" ></LinearLayout>
Inside the Linear Layout, first add an instance of the custom View, changing the package name to suit your own:
<com.example.opacitydraw.DrawingView android:id="@+id/drawing" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_marginBottom="3dp" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:layout_marginTop="3dp" android:layout_weight="1" android:background="#FFFFFFFF" />
We will be adding more to the UI in the next section.
2. Add Opacity Control
Step 1
In this section we will be adding a button for controlling the opacity level of our “brush”.
In your layout, after the custom View, add the button for an opacity control:
<ImageButton android:id="@+id/opacity_btn" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_gravity="center" android:contentDescription="opacity" android:src="@drawable/opacity" />
The button will launch a control through which the user will be able to set the opacity level for drawing. We will use the ID to refer to the button in the Activity code.
Tip: If you are enhancing the drawing series app, add this button in the top section of your layout file alongside the other buttons, using the same layout properties you used for them.
Step 2
You will notice that the Image Button refers to a drawable file. Let’s create this now. Add a new file to your app’s drawables folder(s) and name it “opacity.xml” to match the source attribute we added to the Image Button XML element. Include the following content:
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:dither="true" android:shape="oval" ><size android:height="20dp" android:width="20dp" /><gradient android:endColor="#00000000" android:startColor="#FF000000" android:type="linear" /><stroke android:width="1dp" android:color="#66000000" /></shape>
The shape is a circle with full opacity at one side and full transparency at the other, with a gradient fill between them.
Tip: If you’re working on the drawing series app, you can use the dimension values for the medium size rather than hard-coding a dimension value in your shape drawable.
Step 3
Now let’s design the little pop-up control we want to appear when the user presses the opacity button. Add a new file to your app’s layout folder, naming it “opacity_chooser.xml”. Enter the following layout outline:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" ></LinearLayout>
Inside the Linear Layout, first add some informative text:
<TextView android:id="@+id/opq_txt" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:gravity="center" android:text="100%" android:textStyle="bold" />
We will update the text as the user changes the level, starting at 100% for full opacity initially. We will also use the ID in Java. To set the opacity level, the logical Android UI element is a Seek Bar. With a Seek Bar, the user interacts with a slider control, which we will set to have 0% opacity at the left and 100% on the right. Add the Seek Bar after the Text View:
<SeekBar android:id="@+id/opacity_seek" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_margin="5dp" />
We will also be referring to this in Java. Finally, add an OK button:
<Button android:id="@+id/opq_ok" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="OK" />
When the user clicks OK, we will implement their chosen opacity level for subsequent drawing operations. We will also retain the chosen opacity level if the user launches the control again.
3. Implement Opacity Changes
Step 1
Tip: You can skip to step 2 now if you are enhancing the app from the drawing series. Add the remaining code to your main and custom drawing View classes.
Open your main Activity class. add the following import statements:
import android.app.Dialog; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageButton; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView;
Extend the opening line of the class declaration to implement click listening:
public class MainActivity extends Activity implements OnClickListener
Add an onClick method to your class:
@Override public void onClick(View view){ //respond to clicks }
We will be adding a method to the custom View class to set the opacity level for drawing operations. So that we can do this, add an instance variable to the class, before onCreate:
private DrawingView drawView;
In onCreate, get a reference to the instance of the custom View class you have in your app layout:
drawView = (DrawingView)findViewById(R.id.drawing);
We will be able to call methods on this object to control what happens during drawing.
Step 2
Before the onCreate method in your main Activity, add an instance variable for the opacity button:
private ImageButton opacityBtn;
In onCreate, retrieve a reference to the button:
opacityBtn = (ImageButton)findViewById(R.id.opacity_btn);
Listen for clicks on the button:
opacityBtn.setOnClickListener(this);
In your onClick method, add a conditional test for the opacity button (use an else if if you’re enhancing the series app):
if(view.getId()==R.id.opacity_btn){ //launch opacity chooser }
Step 3
Now we can launch a chooser control for setting the opacity using the layout we defined. Inside the conditional block for the opacity button in onClick, create a Dialog object, setting the title and layout for it:
final Dialog seekDialog = new Dialog(this); seekDialog.setTitle("Opacity level:"); seekDialog.setContentView(R.layout.opacity_chooser);
Retrieve references to the Text View and Seek Bar we included in the layout:
final TextView seekTxt = (TextView)seekDialog.findViewById(R.id.opq_txt); final SeekBar seekOpq = (SeekBar)seekDialog.findViewById(R.id.opacity_seek);
Set the maximum on the Seek Bar:
seekOpq.setMax(100);
We use 100 as the maximum possible alpha level will be 100%.
Step 4
Before we continue with the opacity control, let’s add some required functionality to the custom drawing View class. Start with a new instance variable to store the current opacity level:
private int paintAlpha = 255;
The Paint method we will be using to set the opacity expects a value between 0 and 255. Add a simple get method for the value:
public int getPaintAlpha(){ return Math.round((float)paintAlpha/255*100); }
The level is stored as a value between 0 and 255, but we want to display it as a percentage, so we return a value between 0 and 100. Next, add a method to set the value:
public void setPaintAlpha(int newAlpha){ paintAlpha=Math.round((float)newAlpha/100*255); drawPaint.setColor(paintColor); drawPaint.setAlpha(paintAlpha); }
In the above, we parse the percentage value, set the color, and set the alpha.
Step 5
Back in your main Activity class, in onClick after setting the maximum on the Seek Bar control, first retrieve the existing opacity level:
int currLevel = drawView.getPaintAlpha();
Show this in the Text View and Seek Bar display:
seekTxt.setText(currLevel+"%"); seekOpq.setProgress(currLevel);
Now we want the display to update as the user slides the control up and down. Add the following event listening code:
seekOpq.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { seekTxt.setText(Integer.toString(progress)+"%"); } @Override public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) {} });
We only need to respond to the progress changed event, in which case we update the Text View – this will let the user see their chosen level as a numeric value. Now we need to listen for the user clicking the OK button. After the Seek Bar change listener code block, retrieve an OK button reference:
Button opqBtn = (Button)seekDialog.findViewById(R.id.opq_ok);
Now handle clicks on it:
opqBtn.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { drawView.setPaintAlpha(seekOpq.getProgress()); seekDialog.dismiss(); } });
When the button is clicked, we call the new method we added to change the opacity level, passing the level chosen by the user, and then dismiss the Dialog. Complete the opacity button section of onClick by displaying the Dialog:
seekDialog.show();
Tip: If you’re extending the series app with opacity control, you can either assume that the user wants to use full opacity when they choose a new color, or that they want to retain their chosen opacity level. To reset to 100%, you can call the setPaintAlpha method in the paintClicked method. To retain the chosen alpha level, you can add code to the setColor method to reapply the chosen alpha level after setting the new color.
Conclusion
This completes the opacity drawing functionality! You should be able to run your app, set an opacity level, then see the results in your drawing operations. If you were working on the series app and enhanced it with the pattern fills, notice that the opacity control also applies to drawing with patterns. We have now explored the various typical types of processing in drawing apps, but there are still many more you could try out in your own apps. In a forthcoming tutorial, we will look at how you can accommodate users who are not interacting via touch screens!