Now that I've studied customizing view s recently, I'll take a note: People might as well go to google developer and learn by themselves
Custom views are used to customize views for special effects or to implement complex layouts when you are unwilling to nest multiple views.
First, you need to inherit the View class, or if a feature comparison is incorporated into an existing view, such as LinearLayout, which can be directly inherited, then implement its own function.
After inheritance, you need to implement its construction method, which is mainly used to build built-in variables, which can be set in the xml layout, skipped here, and some initialization needs to be done here, mainly the initialization of the Paint object. It is better not to start the Paint object at ondraw, because it will cause Carton problems.
By overriding the onMesure method, you specify the actual size available for the view, because the width given in his parameters, that is, the size set in the xml, needs to be taken out by MeasureSpec.getSize, in px. If you want to set the size of the control, you need to convert dp to PX with a tool class and set it
int h = MeasureSpec.getSize(heightMeasureSpec);
int w = MeasureSpec.getSize(widthMeasureSpec);
Get size is obtained in the onSizeChange method
//Used to set the size of the content because this parameter displays the actual size
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Log.e (TAG,'onSizeChanged:'+'w:'+'w:' +'h:'+h); //Get the actual width change
//rect is what draws rectangles
mRect = new Rect(0, 0, 150, 150); //Instead of drawing the actual size, onmesure sets the actual size
mWidth = w;
mHeight = h;
mMipmapRect = new Rect(50, 50, 100, 100);
}
Then there is the ondraw method, which is used to draw the interface: it is mainly able to draw circular rectangular images.
Drawing an image is tedious, you need to use the tool class Metrix to do displacement, rotation, scaling and other operations on the image, you can also set the asymptotic change of its color and the occlusion relationship between multiple images through cavas, which are layer-related issues:
LinearGradient mshader=new LinearGradient(50,50,mWidth,mHeight,Color.RED,Color.BLUE, Shader.TileMode.CLAMP);//Length is only available when sizechangge
mLinePaint.setShader(mshader);
This is an asymptotic change in color and several other modes can be used, such as repeat mirroring
Layer masking uses the xformode method, which is unused and can be used to implement rounded pictures.
Loading pictures through the rect class allows you to cut pictures.
The main point is that there are two ways to achieve animation:
Mainly through ondraw's continuous redrawing, that is, calling postInvalidate(), first talk about the reception of gesture sliding: first override the ontouch method, get callbacks, and then use the Detector class to process actions:
//Slide events need to be associated from ontouchevent
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = mDetector.onTouchEvent(event);
return result;
}
/*
How to integrate gesture sensing
*/
class mListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// mScroller.startScroll((int)e1.getX(),(int)e1.getY(),(int)e2.getX(),(int)e2.getY());
// postInvalidate();
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//Imitate scrolling while sliding
mScroller.fling((int) e1.getX(), (int) e1.getY(), (int) velocityX/4, (int) velocityY/4, 0, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
//Operation 1 is done by redrawing the view because onfling is redrawn several times and then animated
if (Build.VERSION.SDK_INT >= 11) {
//valueanimator is a smooth transition to values
mScrollAnimator.setDuration(mScroller.getDuration());
mScrollAnimator.start();
}else{
//First animation
postInvalidate();//Action to update UI after sliding Steps to action by redrawing
}
return true;
}
}
The onDown method must be implemented here and must return true or no callbacks will be accepted
Then you have to talk about the Scroller class, which is a tool class that simulates the position of a sliding drag or fling at different times. It uses getCurX and getCurY to get the position of a certain time to simulate changes in the real world. This is an important class of animation, especially when implementing gesture actions.
The first way is to call a location change when using ondraw:
if(!mScroller.isFinished()){
mScroller.computeScrollOffset();
mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75);
Log.e(TAG, "x is :"+mScroller.getCurrX() );
Log.e(TAG, "y is : "+mScroller.getCurrY() );
}
if (!mScroller.isFinished()) {
postInvalidate();
}
Call mScroller.computeScrollOffset(); calculate a position in time, call refresh to continue calculation before end, it is intuitive
Second way: ValueAnimator is implemented through attribute animation:
The main function of this class is to make callbacks continuously for a certain period of time, similar to an alarm clock, which can set the interval and speed, then call the method, but the redrawing still needs to be done by itself. It does nothing but change the value:
In the init method:
mScrollAnimator=ValueAnimator.ofFloat(0,1);
mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (!mScroller.isFinished()) {
mScroller.computeScrollOffset();
//Calculate Animation Position
mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75);
postInvalidate();
} else {
if (Build.VERSION.SDK_INT >= 11) {
mScrollAnimator.cancel();
}
}
}
});
This keeps redrawing and changing positions, and the Scroller class is important, such as fling, which helps calculate the speed, the position at a time, and the total length of the slide after the end.
Rotation is achieved by setRotate, but it changes the rotation of the entire view.Rotation angle algorithm self-implementation
public class TestView extends View {
private Paint mPaint;
private Paint mTextPaint;
private Paint mLinePaint;
private Paint bitmapPaint;
private Bitmap mBitmap;//bitmap object to draw
private int drawableWidth;
private int drawbleHeight;
private Paint circlePaint;
private GestureDetector mDetector;
//work with graphics
Matrix mMatrix;
String TAG = "view";
int mWidth;
int mHeight;
Rect mMipmapRect;
private int angle = 0;
//Get Sliding Data
private Scroller mScroller;
// Animation Usage Class
private ValueAnimator mScrollAnimator;
/*
How to integrate gesture sensing
*/
class mListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// mScroller.startScroll((int)e1.getX(),(int)e1.getY(),(int)e2.getX(),(int)e2.getY());
// postInvalidate();
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//X y of e1 starting point; x y of e2 current point is coordinate information
float scrollTheta = vectorToScalarScroll(
velocityX,
velocityY,
e2.getX() - mWidth / 2,
e2.getY() - mHeight / 2);
// Log.e(TAG, "e1 x :" + e1.getX());
// Log.e(TAG, "e1 y :" + e1.getY());
// Log.e(TAG, "e2 x :" + e2.getX());
// Log.e(TAG, "e2 y :" + e2.getY());
// Log.e(TAG, "velocity X :" + velocityX);
// Log.e(TAG, "velocityY :" + velocityY);
//Imitate scrolling while sliding
mScroller.fling((int) e1.getX(), (int) e1.getY(), (int) velocityX/4, (int) velocityY/4, 0, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
// PosInvalidate(); //Action to update UI after sliding Steps to action by redrawing
//Operation 1 is done by redrawing the view because onfling is redrawn several times and then animated
if (Build.VERSION.SDK_INT >= 11) {
//valueanimator is a smooth transition to values
mScrollAnimator.setDuration(mScroller.getDuration());
mScrollAnimator.start();
}
return true;
}
}
//Slide events need to be associated from ontouchevent
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = mDetector.onTouchEvent(event);
return result;
}
public void setAngle(int angle) {
this.angle = angle;
invalidate();
requestLayout();
}
public TestView(Context context) {
super(context);
init();
}
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
//Initialize paint
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(getResources().getColor(android.R.color.holo_blue_bright));
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//Set Anti-aliasing Effect If I want where my text is
mTextPaint.setColor(getResources().getColor(android.R.color.black));
mTextPaint.setTextSize(DensityUtils.sp2px(getContext(), 12));
// mTextPaint.setShader(); sets the gradation of colors
//Set gradient for line colors Start (pixel x,y) End (x,y) Start color, End color, Gradient mode
//clamp gradient
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// mLinePaint.setColor(Color.RED);
// mLinePaint.setShader(mshader);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(DensityUtils.dp2px(getContext(), 4));
//Drawing
bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.indicator_arrow);//Object to get bitmap
drawableWidth = mBitmap.getWidth();
drawbleHeight = mBitmap.getHeight();
bitmapPaint = new Paint();
mMatrix = new Matrix();
//Draw a circle
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setStyle(Paint.Style.STROKE);
mDetector = new GestureDetector(TestView.this.getContext(), new mListener());
//Create a Scroller object from the current version
if (Build.VERSION.SDK_INT < 11) {
mScroller = new Scroller(getContext());
} else {
mScroller = new Scroller(getContext(), null, true);
}
/**
* Redraw is invoked in the same way as animation and redraw
* This animation works as long as the stop mScrollAnimator.cancel() is not called; there will always be callbacks
* Then in the callback, the drawing parameters are constantly changed to redraw the interior space above the position
* There's nothing like calling a view object, it's no more intuitive than directly inside an ondray, but it makes sense (officially)
* duaration Is the time of non-stop invocation
*
*/
mScrollAnimator=ValueAnimator.ofFloat(0,1);
mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (!mScroller.isFinished()) {
mScroller.computeScrollOffset();
//Calculate Animation Position
mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75);
postInvalidate();
} else {
if (Build.VERSION.SDK_INT >= 11) {
mScrollAnimator.cancel();
}
}
}
});
}
//The actual size drawn is based on the setMeasuredDimension setting to get the set width and height and then draw it
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
int w = MeasureSpec.getSize(widthMeasureSpec);
// Log.e(TAG, "onMeasure: " + "with=" + w + " " + "height=" + h);
//The result is a pixel point, which is the actual pixel size, not that dp needs to be converted to px and then computed
// width=getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec); //You can also set the width and height this way
// height=getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec);
setMeasuredDimension(w, h); //Set width and height to show size when onsizechangge
}
//The last ondraw doesn't need to care too much about efficiency
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setRotation(30);
canvas.drawRect(mRect, mPaint);
/*
Draw text and lines
*/
// canvas.drawText("hello world", 50, 50, mTextPaint);
// //Set Gradient
// LinearGradient mshader=new LinearGradient(50,50,mWidth,mHeight,Color.RED,Color.BLUE, Shader.TileMode.CLAMP); //Length is available at sizechange time
// mLinePaint.setShader(mshader);
// canvas.drawLine(50, 50, mWidth, mHeight, mLinePaint);
/*
Draw a bitmap
*/
// canvas.drawBitmap(mBitmap,0,0,bitmapPaint);
//It's actually time to cut graphics
// canvas.drawBitmap(mBitmap,new Rect(20,20,160,160),new Rect(40,40,150,150),bitmapPaint);
// mMatrix.
// mMatrix.setTranslate(mWidth / 2, mHeight / 2); //Picture panning determines location
//
// //Rotate first, then stretch twice the width of x, with the same height
// mMatrix.postScale(1, 2, mWidth / 2, mHeight / 2);
// mMatrix.postRotate(angle, mWidth / 2, mHeight / 2); //Picture rotates according to angle 1 and initial position
// canvas.drawBitmap(mBitmap, mMatrix, bitmapPaint);
//
// //Draw a circle
// canvas.drawCircle(mWidth/2,mHeight/2,DensityUtils.dp2px(getContext(),70),circlePaint);
// tickScrollAnimation();
/**scroller Is a class that can simulate sliding, based on which a series of values are given and the interface is redrawn
* Redraw with scroller class
* The principle is intuitive, getting the scroller's location back after each call
*
*/
// if(!mScroller.isFinished()){
// mScroller.computeScrollOffset();
// mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75);
// Log.e(TAG, "x is :"+mScroller.getCurrX() );
// Log.e(TAG, "y is : "+mScroller.getCurrY() );
// }
// if (!mScroller.isFinished()) {
// postInvalidate();
// }
/**
* Manipulating motion through animation
* Animation is possible, but how?
* The animation class calls back continuously for a set time and then redraws
*/
}
// private void tickScrollAnimation() {
// if (!mScroller.isFinished()) {
// mScroller.computeScrollOffset();
// setPieRotation(mScroller.getCurrY());
//
// } else {
// if (Build.VERSION.SDK_INT >= 11) {
// mScrollAnimator.cancel();
// }
// onScrollFinished();
//
// }
// }
Rect mRect;
//Used to set the size of the content because this parameter displays the actual size
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Log.e (TAG,'onSizeChanged:'+'w:'+'w:' +'h:'+h); //Get the actual width change
//rect is what draws rectangles
mRect = new Rect(0, 0, 150, 150); //Instead of drawing the actual size, onmesure sets the actual size
mWidth = w;
mHeight = h;
mMipmapRect = new Rect(50, 50, 100, 100);
}
/**
* Rotate Tool Class Calculates Rotation Position
* Helper method for translating (x,y) scroll vectors into scalar rotation of the pie.
*
* @param dx The x component of the current scroll vector.
* @param dy The y component of the current scroll vector.
* @param x The x position of the current touch, relative to the pie center.
* @param y The y position of the current touch, relative to the pie center.
* @return The scalar representing the change in angular position for this scroll.
*/
private static float vectorToScalarScroll(float dx, float dy, float x, float y) {
// get the length of the vector
float l = (float) Math.sqrt(dx * dx + dy * dy);
// decide if the scalar should be negative or positive by finding
// the dot product of the vector perpendicular to (x,y).
float crossX = -y;
float crossY = x;
float dot = (crossX * dx + crossY * dy);
float sign = Math.signum(dot);
return l * sign;
}
}