Sunday, December 29, 2013

Custom Views

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/androidand 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 :
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);
  }
  
 }
 
}



Monday, December 9, 2013

Add Views programmatically

Example of adding two Buttons in a RelativeLayout :

1) Instantiate the RelativeLayout
2) Instantiate the Buttons.
3) Create the LayoutParams for each Buttons.
4) Add Views to their parent. That's the RelativeLayout.

One key point here. The LayoutParams for the child Views must the same type as that of their parent. So in our case both Buttons  LayoutParams have to be RelativeLayout.LayoutParams.

Let's see how this applies to code :

RelativeLayout layout = new RelativeLayout(this); // Instantiate the parent

b3 = new Button(this); b3.setId(1);
b3.setText("Relative Button 3");

b4 = new Button(this); b4.setId(2);
b4.setText("Relative Button 4");

RelativeLayout.LayoutParams b3_lp = new RelativeLayout.LayoutParams(
                   LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);


RelativeLayout.LayoutParams b4_lp = new RelativeLayout.LayoutParams(
                  LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
b4_lp.addRule(RelativeLayout.RIGHT_OF, b3.getId());

layout.addView(b3, b3_lp);
layout.addView(b4, b4_lp);

setContentView(layout);


The result is :



Notice the addRule() which sets the Button b4 to the right of the b3.
To achieve that we have to set Id's for our Buttons so we can refer to the view that acts as an anchor.