Those who are free of resentful thoughts surely find peace. - Buddha
Posted on 20th Aug 2017
Well, since most of custom views are more time consuming than the regular ones, you should only make it, if there is no easier way to implement a specific feature or you have the following issues that custom view could have resolved:
If you have not tried working out a custom view, then this article is a great opportunity to stay closer to drawing your own flat custom view. It will show you the overall view structure, how to implement specific things, how to avoid common mistakes and even how to animate your view!
The very first thing we need to do, is jump into View lifecycle. For some reason Google does not provide an official diagram of view lifecycle, it is quite a widespread misunderstanding between developers that leads to unexpected bugs and issues, so lets keep our eye on it!
Every view starts it’s life from a Constructor. And what it gives us, is a great opportunity to prepare it for an initial drawing, make various calculation, set default values or whatever we need.
But to make our view easy to use and setup, there is useful AttributeSet interface. It’s easy to implement and is definitely worth the time to spend on it, because it will help you (and your team) to setup your view with some static parameters on further screens.
First, create a new file and call it attrs.xml. In that file could be all the attributes for different custom views. As you can see in this example we have a view called PageIndicatorView and single attribute piv_count.
Secondary in your View constructor you need to obtain attributes and use it as shown below.
public PageIndicatorView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.PageIndicatorView);
int count = typedArray.getInt(R.styleable.PageIndicatorView_piv_count,0);
typedArray.recycle();
}
Note:
After parent view calls addView(View) that view will be attached to a window. At this stage our view will know the other views that it is surrounded by. If your view is working with user’s other views located in same layout.xml it is good place to find them by id (which you can set by attributes) and save as a global reference (if needed).
Means that our custom view is on stage to find out it’s own size. It’s very important method, as for most cases you will need your view to have specific size to fit in your layout.
While overriding this method, what you need to do this is to set setMeasuredDimension(int width, int height).
While setting size of a custom view you should handle case, that view could have specific size that user will set in layout.xml or programmatically. To calculate it properly, a few steps need to be done.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
}
3. Check MeasureSpec mode that user set and adjust size of your view (for width and height).
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
}
Note:
take a look at MeasureSpec values:
Before setting final values to setMeasuredDimension just in case check if those values is not negative. That will avoid issues in layout preview.
This method, incorporates assigning a size and position to each of its children. Because of that, we are looking into a flat custom view (that extends a simple View) that does not have any children so there is no reason to override this method.
That’s where the magic happens. Having both Canvas and Paint objects will allow you draw anything you need.
A Canvas instance comes as onDraw parameter, it basicly respond for drawing a different shapes, while Paint object defines that color that shape will get. Simply, Canvas respond for drawing an object, while Paint for styling it. And it used mostly everywhere whether it is going to be a line, circle or rectangle.
While making a custom view, always keep in mind that onDraw calls lots of time, like really alot. While having some changes, scrolling, swiping your will be redrawn. So that’s why even Android Studio recommend to avoid object allocation during onDraw operation, instead to create it once and reuse further on.
Note:
From a view lifecycle diagram you may notices that there are two methods that leads view to redraw itself. invalidate() and requestLayout() methods will help you to make an interactive custom view, that may change its look on runtime. But why there are two of them?
Animations in custom view is frame by frame process. It means that if you for example want to make a circle radius animate from smaller to bigger you will need to increase it one by one and after each step call invalidate() to draw it.
Your best friend in custom view animations is ValueAnimator. This class will help you to animate any value from start to the end with even Interpolator support (if you need).
ValueAnimator animator = ValueAnimator.ofInt(0, 100);
animator.setDuration(1000);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
int newRadius = (int) animation.getAnimatedValue();
}
});
animator.start();
Note:
Don’t forget to call invalidate() every time new animated values comes out.
Good, better, best. Never let it rest. Untill your good is better and your better is best. - St. Jerome