In the previous case, we used some controls of the system to generate our custom controls by combining. The implementation of the custom controls can also be accomplished by inheriting View from the custom class. Beginning with this blog, we inherited View through a custom class to implement some of our custom controls.
We learn through a case, and now we can achieve such an effect.
We create a new class MyToggleButton to inherit View.
Note that you must override the constructor with two parameters, because if we use this class in the layout file, it will be used to instantiate the class, and if not it will crash.
Describes the main methods of creating and displaying a control.
- Execution Constructor Instance Class
- Measurements, through the measure method, need to rewrite the onMeasure method
If it is currently a ViewGroup, it has an obligation to measure its children.
The child has only the right to recommend, that is to say, the child can recommend how tall and wide the control is, and finally the father must decide the width. - The onLayout method needs to be overridden by the layout method.
Specify the location of the control. In general, View does not need to override this method. It is only when ViewGroup is used that it needs to be overridden. - Drawing a view requires rewriting the onDraw method through the draw method
Drawing based on some parameters of the above two methods
So we usually only need to rewrite onMeasure(int,int) method and onDraw(canvas) method to customize View.
The basic operation is completed by three methods: measure(), layou() and draw(), which contain onMeasure(), onLayout() and onDraw() respectively.
Post the code for the MyToggleButton class.
package com.itcast.test0430_2; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import butterknife.BindBitmap; /** * Created by Administrator on 2019/4/30 0030. */ public class MyToggleButton extends View { private Bitmap backgroundBitmap; private Bitmap slidingBitmap; private int slidLeftMax; private Paint paint; /** * If we use this class in the layout file, we will use this constructor to instantiate the class, and if not crash. * @param context * @param attrs */ public MyToggleButton(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(); } private void initView() { paint = new Paint(); paint.setAntiAlias(true);//Setting Anti-aliasing backgroundBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.switch_background); slidingBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.switch_button); slidLeftMax = backgroundBitmap.getWidth() - slidingBitmap.getWidth(); } /** * Measurement of Views * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(backgroundBitmap.getWidth(),backgroundBitmap.getHeight()); } /** * Draw * @param canvas */ @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(backgroundBitmap,0,0,paint); canvas.drawBitmap(slidingBitmap,0,0,paint); } }
Through the above description, I believe you all understand the code. Such a custom View is drawn, and then we use it in the activity_main.xml file.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.itcast.test0430_2.MainActivity"> <com.itcast.test0430_2.MyToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> </RelativeLayout>
Run the project and preview the effect.
Such a static switch has been drawn, and now we have to let the switch change state by clicking.
Let's start with an analysis. The current state is closed. How can we make it open? When we draw the second picture, the left margin is 0, and at this point we have calculated that the opening state needs the left margin, so we just need to modify it like this.
canvas.drawBitmap(slidingBitmap,slidLeftMax,0,paint);
Okay, let's re-run the project and preview the effect.
This makes the switch on. In this case, we can indirectly control the switch state by dynamically changing the value of the left margin.
We reworked the code of the MyToggleButton class.
package com.itcast.test0430_2; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import butterknife.BindBitmap; /** * Created by Administrator on 2019/4/30 0030. */ public class MyToggleButton extends View implements View.OnClickListener { private Bitmap backgroundBitmap; private Bitmap slidingBitmap; /** * Maximum distance to the left */ private int slidLeftMax; private Paint paint; private int slideLeft; /** * If we use this class in the layout file, we will use this constructor to instantiate the class, and if not crash. * * @param context * @param attrs */ public MyToggleButton(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(); } private void initView() { paint = new Paint(); paint.setAntiAlias(true);//Setting Anti-aliasing backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); slidingBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_button); slidLeftMax = backgroundBitmap.getWidth() - slidingBitmap.getWidth(); setOnClickListener(this); } /** * Measurement of Views * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight()); } /** * Draw * * @param canvas */ @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(backgroundBitmap, 0, 0, paint); canvas.drawBitmap(slidingBitmap, slideLeft, 0, paint); } private boolean isOpen = false; @Override public void onClick(View v) { isOpen = !isOpen; if (isOpen) { slideLeft = slidLeftMax; } else { slideLeft = 0; } //Forced Drawing invalidate();//This method results in onDraw() method execution } }
So we finished clicking, running the project, and previewing the effect.
However, this is still a little far from our goal, we continue to achieve the next requirement, switch sliding.
To achieve this requirement, we need to rewrite the onTouchEvent() method to listen for touch events, and then get the coordinates when pressed. But in the event object, there are getX() method and getRawX() method, so which method should we use? What's the difference between the two methods?
I posted two pictures.
I believe you can see the picture at a glance.
We modified the code of the MyToggleButton class.
package com.itcast.test0430_2; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import butterknife.BindBitmap; /** * Created by Administrator on 2019/4/30 0030. */ public class MyToggleButton extends View implements View.OnClickListener { private Bitmap backgroundBitmap; private Bitmap slidingBitmap; /** * Maximum distance to the left */ private int slidLeftMax; private Paint paint; private int slideLeft; /** * If we use this class in the layout file, we will use this constructor to instantiate the class, and if not crash. * * @param context * @param attrs */ public MyToggleButton(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(); } private void initView() { paint = new Paint(); paint.setAntiAlias(true);//Setting Anti-aliasing backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); slidingBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_button); slidLeftMax = backgroundBitmap.getWidth() - slidingBitmap.getWidth(); setOnClickListener(this); } /** * Measurement of Views * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight()); } /** * Draw * * @param canvas */ @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(backgroundBitmap, 0, 0, paint); canvas.drawBitmap(slidingBitmap, slideLeft, 0, paint); } private boolean isOpen = false; @Override public void onClick(View v) { isOpen = !isOpen; if (isOpen) { slideLeft = slidLeftMax; } else { slideLeft = 0; } //Forced Drawing invalidate();//This method results in onDraw() method execution } private float startX; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //1. Record the coordinates pressed startX = event.getX(); break; case MotionEvent.ACTION_MOVE: //2. Record End Value float endX = event.getX(); //3. Calculating offset float distanceX = endX - startX; slideLeft += distanceX; //4. Shielding Illegal Values //5. Refreshing invalidate(); //6. Data Restoration startX = event.getX(); break; case MotionEvent.ACTION_UP: break; } return super.onTouchEvent(event); } }
Now run the project and preview the effect.
It will be found that the switch has been slipped out. Obviously, this phenomenon is not allowed. We will implement the fourth step of shielding the illegal value.
if(slideLeft < 0){ slideLeft = 0; }else if(slideLeft > slidLeftMax){ slideLeft = slidLeftMax; }
Now run the preview.
Now we can't slide the switch out of the control, but I don't know if you have noticed that it can slide to an awkward place, either open or closed, but in the middle of the two. That situation is also not allowed, so we can solve it now. Here's the question.
Re-modify the code of the MyToggleButton class.
package com.itcast.test0430_2; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import butterknife.BindBitmap; /** * Created by Administrator on 2019/4/30 0030. */ public class MyToggleButton extends View implements View.OnClickListener { private Bitmap backgroundBitmap; private Bitmap slidingBitmap; /** * Maximum distance to the left */ private int slidLeftMax; private Paint paint; private int slideLeft; /** * If we use this class in the layout file, we will use this constructor to instantiate the class, and if not crash. * * @param context * @param attrs */ public MyToggleButton(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(); } private void initView() { paint = new Paint(); paint.setAntiAlias(true);//Setting Anti-aliasing backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); slidingBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_button); slidLeftMax = backgroundBitmap.getWidth() - slidingBitmap.getWidth(); setOnClickListener(this); } /** * Measurement of Views * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight()); } /** * Draw * * @param canvas */ @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(backgroundBitmap, 0, 0, paint); canvas.drawBitmap(slidingBitmap, slideLeft, 0, paint); } private boolean isOpen = false; @Override public void onClick(View v) { isOpen = !isOpen; flushView(); } private void flushView() { if (isOpen) { slideLeft = slidLeftMax; } else { slideLeft = 0; } //Forced Drawing invalidate();//This method results in onDraw() method execution } private float startX; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //1. Record the coordinates pressed startX = event.getX(); break; case MotionEvent.ACTION_MOVE: //2. Record End Value float endX = event.getX(); //3. Calculating offset float distanceX = endX - startX; slideLeft += distanceX; //4. Shielding Illegal Values if(slideLeft < 0){ slideLeft = 0; }else if(slideLeft > slidLeftMax){ slideLeft = slidLeftMax; } //5. Refreshing invalidate(); //6. Data Restoration startX = event.getX(); break; case MotionEvent.ACTION_UP: if(slideLeft > slidLeftMax / 2){ //Display button on isOpen = true; }else{ isOpen = false; } flushView(); break; } return true; } }
Run the project and preview the effect.
At this time, although there will be no embarrassment of the last time, but here is another problem. When I slide, it always runs in the opposite direction of my slide. I want it to slide to the right, but it will go to the left, which is obviously not possible. This is due to the simultaneous effects of touch events and click events. Now we can solve this problem.
Modify the code of the MyToggleButton class again.
package com.itcast.test0430_2; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import butterknife.BindBitmap; /** * Created by Administrator on 2019/4/30 0030. */ public class MyToggleButton extends View implements View.OnClickListener { private Bitmap backgroundBitmap; private Bitmap slidingBitmap; /** * Maximum distance to the left */ private int slidLeftMax; private Paint paint; private int slideLeft; /** * If we use this class in the layout file, we will use this constructor to instantiate the class, and if not crash. * * @param context * @param attrs */ public MyToggleButton(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(); } private void initView() { paint = new Paint(); paint.setAntiAlias(true);//Setting Anti-aliasing backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); slidingBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_button); slidLeftMax = backgroundBitmap.getWidth() - slidingBitmap.getWidth(); setOnClickListener(this); } /** * Measurement of Views * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight()); } /** * Draw * * @param canvas */ @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(backgroundBitmap, 0, 0, paint); canvas.drawBitmap(slidingBitmap, slideLeft, 0, paint); } private boolean isOpen = false; /** * true: Click event is valid, slide event is not valid * false: Click event is not valid, slide event is valid */ private boolean isEnableClick = true; @Override public void onClick(View v) { if (isEnableClick) { isOpen = !isOpen; flushView(); } } private void flushView() { if (isOpen) { slideLeft = slidLeftMax; } else { slideLeft = 0; } //Forced Drawing invalidate();//This method results in onDraw() method execution } private float startX; private float lastX; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //1. Record the coordinates pressed lastX = startX = event.getX(); isEnableClick = true; break; case MotionEvent.ACTION_MOVE: //2. Record End Value float endX = event.getX(); //3. Calculating offset float distanceX = endX - startX; slideLeft += distanceX; //4. Shielding Illegal Values if (slideLeft < 0) { slideLeft = 0; } else if (slideLeft > slidLeftMax) { slideLeft = slidLeftMax; } //5. Refreshing invalidate(); //6. Data Restoration startX = event.getX(); if (Math.abs(endX - lastX) > 5) { //Slide isEnableClick = false; } break; case MotionEvent.ACTION_UP: if (!isEnableClick) { if (slideLeft > slidLeftMax / 2) { //Display button on isOpen = true; } else { isOpen = false; } flushView(); } break; } return true; } }
This is our final version of the code, which completes the whole case.