We
get Custom Views by extending an existing Widget ( Button, EditText,
TextView, etc ), something that fits better to the functionality we
want to achieve and by defining attributes for that view.
1.
By extending a View we now have a Class, whose fully qualified name
can be used as an XML tag, to add to the layout file. If the package
is com.androidexamples.customviews and my Custom View's Class name is
MyEditText then the XML code we are going to add is :
<com.androidexamples.customviews.MyEditText
android:id=”@+id/myview”
android:layout_width="match_parent"
android:layout_height="wrap_content"
…
/>
2.
Into res/values/attrs.xml inside a <declare-styleable> tag we
add the attributes for our view. A valid example could be :
<resources>
<declare-styleable>
<attr
name=”attr_color” format=”color” />
<attr
name=”attr_firstname” format=”string” />
</declare-styleable>
</resources>
From now on we can use these attributes
either in our layout file as part of our custom View XML tag, or
through code.
In order to use them in the layout file
we have to first define a custom namespace similar to what android
does with its own attributes. The android default namespace is :
xmlns:android="http://schemas.android.com/apk/res/android”
and it's defined into the
parent element of the layout file.
Respectively our attributes' namespace uses the same format but instead of android we can use whatever name we want ( like “app” or “custom” ) and the schema is defined with our package name instead of the android. So a valid example could be :
Respectively our attributes' namespace uses the same format but instead of android we can use whatever name we want ( like “app” or “custom” ) and the schema is defined with our package name instead of the android. So a valid example could be :
xmlns:app="http://schemas.android.com/apk/res/com.androidexamples.customviews"
Now
we can add the “attr_firstname” to the XML in the form :
app:attr_firstname=”Some
Random Text”
Through
the code we have access to an array that stores each attribute. It is
a special container named TypedArray
and it can get referenced to the attributes with
obtainStyledAttributes().
3. In our Class code it is a
common practise to use an init() method to get the attributes and
assign initial values to them. This method is then passed to our
View's constructor.
4. Then all it's left to do is
to override the onDraw() and onMeasure() methods.
By
overriding onDraw()
we have access to the Canvas object. You can now create your UI for
your View. The styling of the elements is done through a Paint
object.
OnMeasure()
is called with the width and height specifications from the parent
layout element. Those values should be treated as requirements for
the restrictions on width and height measurements we will produce.
Some key points you should keep in mind
( otherwise it leads you to hard ) :
- call recycle() after you get the attributes from TypedArray.
- Don't create Paint objects inside onDraw() method. Views are redrawn frequently and creating objects inside onDraw() its expensive waste of resources.
- After every change on our attributes' values we have to call invalidate() so the system will know there might be a change in its appearance and needs to be redrawn.
The code below gives as a custom EditText which uses Paint to underline each line with a light green color.
public class MyEditText extends EditText { private Rect mRect; private Paint mPaint; private int attrColor; public MyEditText(Context context) { super(context); } public MyEditText(Context context, AttributeSet attrs) { super(context, attrs); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyEditText); attrColor = ta.getInteger(R.styleable.MyEditText_attr_color, 0xff00ff00); ta.recycle(); init(); } public void init() { mRect = new Rect(); mPaint = new Paint(); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(attrColor); //get value from attr } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int count = getLineCount(); Rect r = mRect; Paint p = mPaint; for(int i=0; i< count; i++) { int baseline = getLineBounds(i, r); canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, p); } } }
No comments:
Post a Comment