Android


Creating Custom Views All Versions

1.0
1.1
1.5
1.6
2.0
2.0.1
2.1.x
2.2.x
2.3
2.3.3
3.0.x
3.1.x
3.2.x
4.0
4.0.3
4.1
4.2
4.3
4.4
4.4W
5.0
5.1
6.0
7.0
7.1

This draft deletes the entire topic.

Introduction

Introduction

expand all collapse all

Examples

  • 34

    If you need a completely customized view, you'll need to subclass View (the superclass of all Android views) and provide your custom sizing (onMeasure(...)) and drawing (onDraw(...)) methods:

    1. Create your custom view skeleton: this is basically the same for every custom view. Here we create the skeleton for a custom view that can draw a smiley, called SmileyView:

      public class SmileyView extends View {
          private Paint mCirclePaint;
          private Paint mEyeAndMouthPaint;
      
          private float mCenterX;
          private float mCenterY;
          private float mRadius;
          private RectF mArcBounds = new RectF();
      
          public SmileyView(Context context) {
              this(context, null);
          }
      
          public SmileyView(Context context, AttributeSet attrs) {
              this(context, attrs, 0);
          }
      
          public SmileyView(Context context, AttributeSet attrs, int defStyleAttr) {
              super(context, attrs, defStyleAttr);
              initPaints();
          }
      
          private void initPaints() {/* ... */}
      
          @Override
          protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {/* ... */}
      
          @Override
          protected void onDraw(Canvas canvas) {/* ... */}
      }
      
    2. Initialize your paints: the Paint objects are the brushes of your virtual canvas defining how your geometric objects are rendered (e.g. color, fill and stroke style, etc.). Here we create two Paints, one yellow filled paint for the circle and one black stroke paint for the eyes and the mouth:

      private void initPaints() {
          mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
          mCirclePaint.setStyle(Paint.Style.FILL);
          mCirclePaint.setColor(Color.YELLOW);
          mEyeAndMouthPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
          mEyeAndMouthPaint.setStyle(Paint.Style.STROKE);
          mEyeAndMouthPaint.setStrokeWidth(16 * getResources().getDisplayMetrics().density);
          mEyeAndMouthPaint.setStrokeCap(Paint.Cap.ROUND);
          mEyeAndMouthPaint.setColor(Color.BLACK);
      }
      
    3. Implement your own onMeasure(...) method: this is required so that the parent layouts (e.g. FrameLayout) can properly align your custom view. It provides a set of measureSpecs that you can use to determine your view's height and width. Here we create a square by making sure that the height and width are the same:

      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          int w = MeasureSpec.getSize(widthMeasureSpec);
          int h = MeasureSpec.getSize(heightMeasureSpec);
      
          int size = Math.min(w, h);
          setMeasuredDimension(size, size);
      }
      

      Note that onMeasure(...) must contain at least one call to setMeasuredDimension(..) or else your custom view will crash with an IllegalStateException.

    4. Implement your own onSizeChanged(...) method: this allows you to catch the current height and width of your custom view to properly adjust your rendering code. Here we just calculate our center and our radius:

      @Override
      protected void onSizeChanged(int w, int h, int oldw, int oldh) {
          mCenterX = w / 2f;
          mCenterY = h / 2f;
          mRadius = Math.min(w, h) / 2f;
      }
      
    5. Implement your own onDraw(...) method: this is where you implement the actual rendering of your view. It provides a Canvas object that you can draw on (see the official Canvas documentation for all drawing methods available).

      @Override
      protected void onDraw(Canvas canvas) {
          // draw face
          canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint);
          // draw eyes
          float eyeRadius = mRadius / 5f;
          float eyeOffsetX = mRadius / 3f;
          float eyeOffsetY = mRadius / 3f;
          canvas.drawCircle(mCenterX - eyeOffsetX, mCenterY - eyeOffsetY, eyeRadius, mEyeAndMouthPaint);
          canvas.drawCircle(mCenterX + eyeOffsetX, mCenterY - eyeOffsetY, eyeRadius, mEyeAndMouthPaint);
          // draw mouth
          float mouthInset = mRadius /3f;
          mArcBounds.set(mouthInset, mouthInset, mRadius * 2 - mouthInset, mRadius * 2 - mouthInset);
          canvas.drawArc(mArcBounds, 45f, 90f, false, mEyeAndMouthPaint);
      }
      
    6. Add your custom view to a layout: the custom view can now be included in any layout files that you have. Here we just wrap it inside a FrameLayout:

      <FrameLayout
          xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <com.example.app.SmileyView
              android:layout_width="match_parent"
              android:layout_height="match_parent" />
      </FrameLayout>
      

    Note that it is recommended to build your project after the view code is finished. Without building it you won't be able to see the view on a preview screen in Android Studio.

    After putting everything together, you should be greeted with the following screen after launching the activity containing the above layout:

    SmileyView used inside activity

  • 16

    Custom views can also take custom attributes which can be used in Android layout resource files. To add attributes to your custom view you need to do the following:

    1. Define the name and type of your attributes: this is done inside res/values/attrs.xml (create it if necessary). The following file defines a color attribute for our smiley's face color and an enum attribute for the smiley's expression:

      <resources>
          <declare-styleable name="SmileyView">
              <attr name="smileyColor" format="color" />
              <attr name="smileyExpression" format="enum">
                  <enum name="happy" value="0"/>
                  <enum name="sad" value="1"/>
              </attr>
          </declare-styleable>
          <!-- attributes for other views -->
      </resources>
      
    2. Use your attributes inside your layout: this can be done inside any layout files that use your custom view. The following layout file creates a screen with a happy yellow smiley:

      <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          android:layout_height="match_parent"
          android:layout_width="match_parent">
          
          <com.example.app.SmileyView
              android:layout_height="56dp"
              android:layout_width="56dp"
              app:smileyColor="#ffff00"
              app:smileyExpression="happy" />
      </FrameLayout>
      

      Tip: Custom attributes do not work with the tools: prefix in Android Studio 2.1 and older (and possibly in future versions). In this example, replacing app:smileyColor with tools:smileyColor would result in smileyColor neither being set during runtime nor at design time.

    3. Read your attributes: this is done inside your custom view source code. The following snippet of SmileyView demonstrates how the attributes can be extracted:

      public class SmileyView extends View {
          // ...
      
          public SmileyView(Context context) {
              this(context, null);
          }
      
          public SmileyView(Context context, AttributeSet attrs) {
              this(context, attrs, 0);
          }
      
          public SmileyView(Context context, AttributeSet attrs, int defStyleAttr) {
              super(context, attrs, defStyleAttr);
              
              TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SmileyView, defStyleAttr, 0);
              mFaceColor = a.getColor(R.styleable.SmileyView_smileyColor, Color.TRANSPARENT);
              mFaceExpression = a.getInteger(R.styleable.SmileyView_smileyExpression, Expression.HAPPY);
              // Important: always recycle the TypedArray
              a.recycle();
      
              // initPaints(); ...
          }
      }
      
    4. (Optional) Add default style: this is done by adding a style with the default values and loading it inside your custom view. The following default smiley style represents a happy yellow one:

      <!-- styles.xml -->
      <style name="DefaultSmileyStyle">
          <item name="smileyColor">#ffff00</item>
          <item name="smileyExpression">happy</item>
      </style>
      

      Which gets applied in our SmileyView by adding it as the last parameter of the call to obtainStyledAttributes (see code in step 3):

      TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SmileyView, defStyleAttr, R.style.DefaultSmileyViewStyle);
      

      Note that any attribute values set in the inflated layout file (see code in step 2) will override the corresponding values of the default style.

    5. (Optional) Provide styles inside themes: this is done by adding a new style reference attribute which can be used inside your themes and providing a style for that attribute. Here we simply name our reference attribute smileyStyle:

      <!-- attrs.xml -->
      <attr name="smileyStyle" format="reference" />
      

      Which we then provide a style for in our app theme (here we just reuse the default style from step 4):

      <!-- themes.xml -->
      <style name="AppTheme" parent="AppBaseTheme">
          <item name="smileyStyle">@style/DefaultSmileyStyle</item>
      </style>
      
  • 6

    Do not allocate new objects in onDraw

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint(); //Do not allocate here
    }
    

    Instead of drawing drawables in canvas...

    drawable.setBounds(boundsRect);
    
    drawable.draw(canvas);
    

    Use a Bitmap for faster drawing:

    canvas.drawBitmap(bitmap, srcRect, boundsRect, paint);
    

    Do not redraw the entire view to update just a small part of it. Instead redraw the specific part of view.

    invalidate(boundToBeRefreshed);
    

    If your view is doing some continuous animation, for instance a watch-face showing each and every second, at least stop the animation at onStop() of the activity and start it back on onStart() of the activity.

    Do not do any calculations inside the onDraw method of a view, you should instead finish drawing before calling invalidate(). By using this technique you can avoid frame dropping in your view.

    Rotations

    The basic operations of a view are translate, rotate, etc... Almost every developer has faced this problem when they use bitmap or gradients in their custom view. If the view is going to show a rotated view and the bitmap has to be rotated in that custom view, many of us will think that it will be expensive. Many think that rotating a bitmap is very expensive because in order to do that, you need to translate the bitmap's pixel matrix. But the truth is that it is not that tough! Instead of rotating the bitmap, just rotate the canvas itself!

    // Save the canvas state

    int save = canvas.save();
    // Rotate the canvas by providing the  center point as pivot and angle
    canvas.rotate(pivotX, pivotY, angle);
    // Draw whatever you want
    // Basically whatever you draw here will be drawn as per the angle you rotated the canvas
    canvas.drawBitmap(...);
    // Now restore your your canvas to its original state
    canvas.restore(save);
    // Unless canvas is restored to its original state, further draw will also be rotated.
    
Please consider making a request to improve this example.

Syntax

Syntax

Parameters

Parameters

Remarks

Remarks

Still have a question about Creating Custom Views? Ask Question

Topic Outline