Android自定义view详解,使用实例,自定义属性,贝塞尔曲线
//只會觸發執行onDraw方法,只會改變繪制里面的內容,條目的繪制
invalidate();?
?//只會觸發執行onDraw方法,但是可以在子線程中刷新
postInvalidate();
?//view的布局參數改變之后刷新,比如view的寬度和高度都修改了,只能通過requestLayout()方法刷新
requestLayout();
?
前言
- 自定義View原理是Android開發者必須了解的基礎;
- 在了解自定義View之前,你需要有一定的知識儲備;
- 本文將全面解析關于自定義View中的所有知識基礎。
目錄
1. View的分類
視圖View主要分為兩類:
| 單一視圖 | 即一個View,如TextView | 不包含子View |
| 視圖組 | 即多個View組成的ViewGroup,如LinearLayout | 包含子View |
2. View類簡介
- View類是Android中各種組件的基類,如View是ViewGroup基類
-
View表現為顯示在屏幕上的各種視圖
Android中的UI組件都由View、ViewGroup組成。
-
View的構造函數:共有4個,具體如下:
自定義View必須重寫至少一個構造函數:
更加具體的使用請看:深入理解View的構造函數和
理解View的構造函數
3. View視圖結構
對于多View的視圖,結構是樹形結構:最頂層是ViewGroup,ViewGroup下可能有多個ViewGroup或View,如下圖:
請記住:無論是measure過程、layout過程還是draw過程,永遠都是從View樹的根節點開始測量或計算(即從樹的頂端開始),一層一層、一個分支一個分支地進行(即樹形遞歸),最終計算整個View樹中各個View,最終確定整個View樹的相關屬性。
4. Android坐標系
Android的坐標系定義為:
- 屏幕的左上角為坐標原點
- 向右為x軸增大方向
- 向下為y軸增大方向
具體如下圖:
注:區別于一般的數學坐標系
5. View位置(坐標)描述
- View的位置由4個頂點決定的(如下A、B、C、D)
4個頂點的位置描述分別由4個值決定:
(請記住:View的位置是相對于父控件而言的)
- Top:子View上邊界到父view上邊界的距離
- Left:子View左邊界到父view左邊界的距離
- Bottom:子View下邊距到父View上邊界的距離
- Right:子View右邊界到父view左邊界的距離
如下圖:
個人建議:按頂點位置來記憶:
- Top:子View左上角距父View頂部的距離;
- Left:子View左上角距父View左側的距離;
- Bottom:子View右下角距父View頂部的距離
- Right:子View右下角距父View左側的距離
6. 位置獲取方式
- View的位置是通過view.getxxx()函數進行獲取:(以Top為例)
- 與MotionEvent中 get()和getRaw()的區別
具體如下圖:
7. Android的角度(angle)與弧度(radian)
-
自定義View實際上是將一些簡單的形狀通過計算,從而組合到一起形成的效果。
這會涉及到畫布的相關操作(旋轉)、正余弦函數計算等,即會涉及到角度(angle)與弧度(radian)的相關知識。
-
角度和弧度都是描述角的一種度量單位,區別如下圖::
在默認的屏幕坐標系中角度增大方向為順時針。
注:在常見的數學坐標系中角度增大方向為逆時針
8. Android中顏色相關內容
Android中的顏色相關內容包括顏色模式,創建顏色的方式,以及顏色的混合模式等。
8.1 顏色模式
Android支持的顏色模式:
以ARGB8888為例介紹顏色定義:
8.2 定義顏色的方式
8.2.1 在java中定義顏色
//java中使用Color類定義顏色int color = Color.GRAY; //灰色//Color類是使用ARGB值進行表示int color = Color.argb(127, 255, 0, 0); //半透明紅色int color = 0xaaff0000; //帶有透明度的紅色8.2.2 在xml文件中定義顏色
在/res/values/color.xml 文件中如下定義:
<?xml version="1.0" encoding="utf-8"?> <resources>//定義了紅色(沒有alpha(透明)通道)<color name="red">#ff0000</color>//定義了藍色(沒有alpha(透明)通道)<color name="green">#00ff00</color> </resources>在xml文件中以”#“開頭定義顏色,后面跟十六進制的值,有如下幾種定義方式:
#f00 //低精度 - 不帶透明通道紅色#af00 //低精度 - 帶透明通道紅色#ff0000 //高精度 - 不帶透明通道紅色#aaff0000 //高精度 - 帶透明通道紅色8.3 引用顏色的方式
8.3.1 在java文件中引用xml中定義的顏色:
//方法1 int color = getResources().getColor(R.color.mycolor);//方法2(API 23及以上) int color = getColor(R.color.myColor);8.3.2 在xml文件(layout或style)中引用或者創建顏色
<!--在style文件中引用--><style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"><item name="colorPrimary">@color/red</item></style><!--在layout文件中引用在/res/values/color.xml中定義的顏色-->android:background="@color/red" <!--在layout文件中創建并使用顏色-->android:background="#ff0000"--------------------- 作者:Carson_Ho 來源:CSDN 原文:https://blog.csdn.net/carson_ho/article/details/56009827?utm_source=copy 版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
?
使用實例:
?
1、Android自定義View構造函數詳解
public DragBallView(Context context) {
? ? this(context, null);
? ? LgqLogutil.e("1");
}
?
public DragBallView(Context context, @Nullable AttributeSet attrs) {
? ? this(context, attrs, 1);
? ? LgqLogutil.e("2");
}
?
public DragBallView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
? ? super(context, attrs, defStyleAttr);
? ? initPaint();
? ? initPoint();
?
}
new引用,調用第一個構造函數
DragBallView dragBallView = new DragBallView(this);
xml文件引用調用第二個構造函數
<com.tianxin.ttttest.DragBallView
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent"
? ? android:id="@+id/mydrag"/>
第三個可在第二個函數調用之
?
2、單位轉換
/**
?* dp 2 px圖片
?*
?* @param dpVal
?*/
protected int dp2px(int dpVal) {
? ? return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
? ? ? ? ? ? dpVal, getResources().getDisplayMetrics());
}
?
/**
?* sp 2 px文字
?*
?* @param spVal
?* @return
?*/
protected int sp2px(int spVal) {
? ? return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
? ? ? ? ? ? spVal, getResources().getDisplayMetrics());
}
3、判斷觸摸點是否在View范圍之內
//是否可拖拽
private boolean mIsCanDrag = false;
/**
?* 判斷是否可以拖拽
?*
?* @param event event
?*/
private void setIsCanDrag(MotionEvent event) {
? ? Rect rect = new Rect();
? ? rect.left = (int) (startX - radiusStart);
? ? rect.top = (int) (startY - radiusStart);
? ? rect.right = (int) (startX + radiusStart);
? ? rect.bottom = (int) (startY + radiusStart);
?
? ? //觸摸點是否在圓的坐標域內
? ? mIsCanDrag = rect.contains((int) event.getX(), (int) event.getY());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
?
? ? float currentX;
? ? float currentY;
?
? ? switch (event.getAction()) {
?
? ? ? ? case MotionEvent.ACTION_DOWN:
? ? ? ? ? ? setIsCanDrag(event);
4、實時刷新onDraw方法
invalidate();
5、實現構造函數方法以及onSizeChanged方法和onDraw方法即可實現自定義View
onSizeChanged初始化數據
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
? ? super.onSizeChanged(w, h, oldw, oldh);
? ? startX = w / 2;
? ? startY = h / 2;
onDraw方法實現
(1)繪制背景。由private方法drawBackground()完成。如果未設定背景Drawable對象,則會直接返回。?
(2)繪制內容。由onDraw()方法完成。?
(3)繪制子視圖。由dispatchView()完成。View的dispatchView()是空方法,ViewGroup的dispatchView()有具體實現,主要是調用子視圖的draw()方法。?
(4)繪制裝飾。主要是foreground與滾動條。?
因此,只要最高層的根布局調用了它的draw()方法,低層的所有view的draw()方法最終都會被調用。因此,大部分情況下draw()方法是不需要手動調用的,我們只需要覆蓋onDraw()方法,將自己的內容繪制出來即可。
?
畫圓
canvas.drawCircle(pointF.x, pointF.y, radius, circlePaint);
cx:圓心的x坐標。
cy:圓心的y坐標。
radius:圓的半徑。
paint:繪制時所使用的畫筆
畫框:
? ? ? ? mPaint.setStrokeWidth((float) 3.0); ? ? ? ? ? ? ?//線寬
? ? ? ? mPaint.setStyle(Paint.Style.STROKE); ? ? ? ? ? ? ? ? ? //空心效果
畫線
canvas.drawLine(startX, startY, startX, endY, mCenterLinePaint);虛線
int dp4 = Utils.dp2px(context, 4); dashLine[0] = dp4; dashLine[1] = dp4; void drawLine(Canvas canvas, float startX, float startY, float length) {int index = (int) ((length / (dashLine[0] + dashLine[1]))) + 1;float endY = startY;for (int i = 0; i < index; i++) {endY += dashLine[0];canvas.drawLine(startX, startY, startX, endY, mCenterLinePaint); // endY += dashLine[1];endY += 20;startY = endY;}}畫矩形
canvas.drawRect(textRect,testpaint);
?橢圓
private RectF mSeatNoRectF = new RectF(); canvas.drawRoundRect(mSeatNoRectF, mSeatNoTopMargin, mSeatNoTopMargin, mSeatNoPaint);畫字體
canvas.drawText(msgCount > 99 ? "99+" : msgCount + "", textRect.centerX(), ?baseline, textPaint);
畫貝塞爾曲線
private void drawBezier(Canvas canvas) {
? ? path.reset();
? ? path.moveTo(pointA.x, pointA.y);
? ? path.quadTo(pointO.x, pointO.y, pointB.x, pointB.y);
? ? path.lineTo(pointC.x, pointC.y);
? ? path.quadTo(pointO.x, pointO.y, pointD.x, pointD.y);
? ? path.lineTo(pointA.x, pointA.y);
? ? path.close();
?
? ? canvas.drawPath(path, circlePaint);
}
附:
textRect.left = (int) (point.x - radiusStart+15);
textRect.top = (int) (point.y - radiusStart);
textRect.right = (int) (point.x + radiusStart-15);
textRect.bottom = (int) (point.y + radiusStart);
Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
畫滿:
mPaint.setStyle(Paint.Style.FILL);?
自定義屬性
?
1、values文件夾下創建attrs文件夾
<?xml version="1.0" encoding="utf-8"?> <resources><declare-styleable name="CircleView"><attr name="radius" format="integer"/><attr name="CircleX" format="integer"/><attr name="CircleY" format="integer"/><attr name="age"><flag name='child' value="10"/><flag name="young" value="18"/><flag name="old" value="60"/></attr></declare-styleable> </resources>?
2、?自定義view使用自定義屬性
?
3、XML文件調用自定義屬性
?
?貝塞爾曲線
/*** @author : LGQ* @date : 2020/09/08 15* @desc :*/ public class WaveBezierView extends View implements View.OnClickListener {private Path mPath;private Paint mPaintBezier;private int mWaveLength;private int mScreenHeight;private int mScreenWidth;private int mCenterY;private int mWaveCount;private ValueAnimator mValueAnimator;//波浪流動X軸偏移量private int mOffsetX;//波浪升起Y軸偏移量private int mOffsetY;private int count = 0;private OnItemClickListener mOnItemClickListener;public WaveBezierView(Context context) {super(context);}public WaveBezierView(Context context, AttributeSet attrs) {super(context, attrs);mPaintBezier = new Paint(Paint.ANTI_ALIAS_FLAG);mPaintBezier.setColor(Color.LTGRAY);mPaintBezier.setStrokeWidth(8);mPaintBezier.setStyle(Paint.Style.STROKE); // mPaintBezier.setStyle(Paint.Style.FILL_AND_STROKE);mWaveLength = 800;}public WaveBezierView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mPath = new Path();setOnClickListener(this);mScreenHeight = h;mScreenWidth = w;mCenterY = h / 2;//設定波浪在屏幕中央處顯示//此處多加1,是為了預先加載屏幕外的一個波浪,持續報廊移動時的連續性mWaveCount = (int) Math.round(mScreenWidth / mWaveLength + 1.5);Log.i("lgq","波谷數==== "+mWaveCount+"....."+(mScreenWidth / mWaveLength + 1.5)+"mScreenWidth=="+mScreenWidth);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mPath.reset();//Y坐標每次繪制時減去偏移量,即波浪升高mPath.moveTo(-mWaveLength + mOffsetX, mCenterY); // Log.i("lgq","bogu起點==== "+(-mWaveLength + mOffsetX));//每次循環繪制兩個二階貝塞爾曲線形成一個完整波形(含有一個上拱圓,一個下拱圓)for (int i = 0; i < mWaveCount; i++) {//此處的60是指波浪起伏的偏移量,自定義為60mPath.quadTo(-mWaveLength * 3 / 4 + i * mWaveLength + mOffsetX, mCenterY + 60, -mWaveLength / 2 + i * mWaveLength + mOffsetX, mCenterY);mPath.quadTo(-mWaveLength / 4 + i * mWaveLength + mOffsetX, mCenterY - 60, i * mWaveLength + mOffsetX, mCenterY);//第二種寫法:相對位移 // mPath.rQuadTo(mWaveLength / 4, -60, mWaveLength / 2, 0); // mPath.rQuadTo(mWaveLength / 4, +60, mWaveLength / 2, 0);}mPath.lineTo(mScreenWidth, mScreenHeight);mPath.lineTo(0, mScreenHeight);mPath.close();canvas.drawPath(mPath, mPaintBezier);}@Overridepublic void onClick(View view) {mOnItemClickListener.onItemClick(view,3);//設置動畫運動距離mValueAnimator = ValueAnimator.ofInt(0, mWaveLength);mValueAnimator.setDuration(1000);//設置播放數量無限循環mValueAnimator.setRepeatCount(ValueAnimator.INFINITE); // mValueAnimator.setRepeatCount(1);//設置線性運動的插值器mValueAnimator.setInterpolator(new LinearInterpolator());mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator valueAnimator) {//獲取偏移量,繪制波浪曲線的X橫坐標加上此偏移量,產生移動效果mOffsetX = (int) valueAnimator.getAnimatedValue();count++;invalidate();}});mValueAnimator.start();}public interface OnItemClickListener {void onItemClick(View view, int position);}public void setOnItemClickListener(OnItemClickListener onItemClickListener) {this.mOnItemClickListener = onItemClickListener;} }?
下載資源:https://blog.csdn.net/Samlss/article/details/81189576#PeasLoadingView_loading_viewhttpsgithubcomsamlssPeasLoadingView_53?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Android自定义view详解,使用实例,自定义属性,贝塞尔曲线的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: xml注释方法
- 下一篇: 复变函数 —— 2. 复函数的导数与复变