日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

自定义控件android.r,Android控件架构与自定义控件

發(fā)布時間:2024/9/27 Android 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 自定义控件android.r,Android控件架构与自定义控件 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

最近在開發(fā)的路上越走越遠了,每天在看各位大神公眾號更新內(nèi)容是自定義View的時候,一些小的內(nèi)容有點模具,決定回過頭來溫習一下過往的內(nèi)容。此篇也是根據(jù)android群英傳來總結(jié)的一篇文章。

1 Android控件架構(gòu)

Android的每個控件都是占一塊矩形的區(qū)域,大致的分兩類,繼承View和ViewGroup,ViewGroup相當于一個容器,他可以管理多個字View,整個界面上的控件形成了一個樹形結(jié)構(gòu),也就是我們常說的控件樹,上層控件負責下層控件的測量和繪制,并且傳遞交互事件,通過findviewbyid()這個方法來獲取,其實就是遍歷查找,在樹形圖的頂部都有一個ViewParent對象,這就是控制核心,所有的交互管理事件都是由它統(tǒng)一調(diào)度和分配,從而進行整個視圖的控制

2 View的測量

我們想要繪制一個View,首先還是得知道這個View的大小,系統(tǒng)是如何把他繪制出來的,在Android中,我們要想繪制一個View,就必須要知道這個View的大小,然后告訴系統(tǒng),這個過程在onMeasure()中進行。

Android給我們提供了一個設(shè)計短小精悍的類——MeasureSpec類,通過他來幫助我們測量View, MeasureSpec是一個32位的int值,其中高2位為測量模式,低30為測量的大小,在計算中使用位運算時為了提高并且優(yōu)化效率

三種測量模式如下:

EXACTLY 精確值模式

表示父視圖希望子視圖的大小應(yīng)該是由specSize的值來決定的,系統(tǒng)默認會按照這個規(guī)則來設(shè)置子視圖的大小,開發(fā)人員當然也可以按照自己的意愿設(shè)置成任意的大小。

AT_MOST最大值模式

控件的尺寸不超過父控件允許的最大尺寸即可

UNSPECIFIED

表示開發(fā)人員可以將視圖按照自己的意愿設(shè)置成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。

一份模板代碼:

private int measureWidth(int measureSpec) {

int result = 0;

int specMode = MeasureSpec.getMode(measureSpec);

int specSize = MeasureSpec.getSize(measureSpec);

if (specMode == MeasureSpec.EXACTLY) {

result = specSize;

} else {

result = 200;

if (specMode == MeasureSpec.AT_MOST) {

result = Math.min(result, specSize);

}

}

return result;

}

3 View的繪制

Canvas顧名思義,畫布的意思,而onDraw()就一個參數(shù),就是Canvas了,我們要在其他地方繪制的話,就需要new對象了

Canvas canvas = new Canvas(Bitmap);

//繪制直線

canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);

//繪制矩形

canvas.drawRect(float left, float top, float right, float bottom, Paint paint);

//繪制圓形

canvas.drawCircle(float cx, float cy, float radius, Paint paint);

//繪制字符

canvas.drawText(String text, float x, float y, Paint paint);

//繪制圖形

canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint);

4 ViewGroup的測量

前面也說過,ViewGroup是老大,他是用來管理View的,包括View的大小什么的,當我們的ViewGroup大小是包裹內(nèi)容的時候,實際上ViewGroup會遍歷所有的子View,來獲取View的大小,從而決定自身的大小,而在其他模式下,會通過具體的值來自定自身的大小

ViewGroup遍歷所有的View會調(diào)用所有的View的onMeasure()方法來獲取測量結(jié)果,當子View測量完畢之后,,就需要將子View放在合適的地方,這部分是由onLayout()來進行的,在我們自定義ViewGroup的時候,一般都要重寫onLayout()方法控制子View顯示位置的邏輯,同樣,如果需要wrap_content屬性,那就必須重寫onLayout()方法了,這點和View是相同的

5 ViewGroup的繪制

ViewGroup在一般情況下是不會繪制的,因為他本身沒有需要繪制的東西,如果不是指定ViewGroup的背景顏色,他連onDraw()都不會調(diào)用,但是ViewGroup會使用dispatchDraw()來繪制其他子View,其過程同樣是遍歷所喲普的子View,并調(diào)用子View的繪制方法來完成繪制的

6 自定義View

自定義View一直是個難點,Android自帶的控件很難滿足我們的需求,所欲我們需要重寫控件或者自定義一個View,但是一般強大的View,都還是存在少許的bug的,而且現(xiàn)在Android ROM的多樣性,適配問題也越來越麻煩了,當然,自定義View你熟悉之后,可以了解系統(tǒng)繪制控件的原理,而且能讓你的APP更加美觀,強大。

在View中通常有以下比較重要的回調(diào)方法

首先,我們應(yīng)該了解一下比較重要的回調(diào)方法:

onDraw() 繪制View的顯示內(nèi)容

onMeasure() 使用此方法時多是該View支持wrap_content屬性

onFinishInflate() 從XML加載組件后回調(diào)

onSizeChanged() 組件大小改變后回調(diào)

onMeasure(int widthMeasureSpec, int heightMeasureSpec)回調(diào)該方法進行測量

onLayout(boolean changed, int left, int top, int right, int bottom)回調(diào)該方法確定顯示位置

onTouchEvent(MotionEvent event) 監(jiān)聽到觸摸事件時的回調(diào)

以上就是幾種常用的回調(diào)的方法.上面的方法并不需要全部寫出來,看個人需要,一般我們實現(xiàn)自定義控件有三種方法

對現(xiàn)有的控件進行擴展

通過組件來實現(xiàn)新的控件

重寫View來實現(xiàn)全新的控件

6.1 對現(xiàn)有控件進行擴展

這是一個我們十分常用的一個方法,用來對現(xiàn)有的控件進行擴展,比如TextView需要漸變啊什么的,挺常用的,這里我們就來寫一個小栗子,我們先來看下效果

public class MyTextView extends TextView {

private Paint mPaint1, mPaint2;

public MyTextView(Context context) {

super(context);

initView();

}

public MyTextView(Context context, AttributeSet attrs) {

super(context, attrs);

initView();

}

public MyTextView(Context context, AttributeSet attrs,

int defStyleAttr) {

super(context, attrs, defStyleAttr);

initView();

}

private void initView() {

mPaint1 = new Paint();

mPaint1.setColor(getResources().getColor(

android.R.color.holo_blue_light));

mPaint1.setStyle(Paint.Style.FILL);

mPaint2 = new Paint();

mPaint2.setColor(Color.YELLOW);

mPaint2.setStyle(Paint.Style.FILL);

}

@Override

protected void onDraw(Canvas canvas) {

// 繪制外層矩形

canvas.drawRect(

0,

0,

getMeasuredWidth(),

getMeasuredHeight(),

mPaint1);

// 繪制內(nèi)層矩形

canvas.drawRect(

10,

10,

getMeasuredWidth() - 10,

getMeasuredHeight() - 10,

mPaint2);

canvas.save();

// 繪制文字前平移10像素

canvas.translate(10, 0);

// 父類完成的方法,即繪制文本

super.onDraw(canvas);

canvas.restore();

}}

一個稍微復(fù)雜的TextView:

這個可以利用LinearGradient,Shader,Matrix,來完成,來實現(xiàn)一個閃閃發(fā)光的閃動效果,我們充分的利用Shader渲染器,來設(shè)置一個不斷變化的LinearGradient,首先我們要在onSizeChanged()方法中完成一些初始化操作

public class CoolTextView extends TextView {

private int mViewWidth;

//初始化畫筆

private Paint mPaint;

//渲染器

private LinearGradient mLinearGradient;

//矩陣

private Matrix matrix;

private int mTranslate;

public CoolTextView(Context context) {

super(context);

}

public CoolTextView(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

mPaint=new Paint();

if (mViewWidth==0){

mViewWidth=getMeasuredWidth();

if (mViewWidth>0){

//獲取當前TextView的畫筆

mPaint=getPaint();

//渲染器

mLinearGradient=new LinearGradient(0,0,mViewWidth,0,

new int[]{Color.BLUE, 0xffffffff, Color.BLUE},

null, Shader.TileMode.CLAMP);

mPaint.setShader(mLinearGradient);

matrix=new Matrix();

}

}

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if (matrix!=null){

//修改可以改變顯示的速度

mTranslate+=mViewWidth/10;

if (mTranslate>1*mViewWidth){

mTranslate=-mViewWidth;

}

matrix.setTranslate(mTranslate,0);

mLinearGradient.setLocalMatrix(matrix);

//每隔100毫秒閃動一下

postInvalidateDelayed(100);

}

}

/*

LinearGradient參數(shù):

float x0: 漸變起始點x坐標

float y0:漸變起始點y坐標

float x1:漸變結(jié)束點x坐標

float y1:漸變結(jié)束點y坐標

int[] colors:顏色 的int 數(shù)組

float[] positions: 相對位置的顏色數(shù)組,可為null, 若為null,可為null,顏色沿漸變線均勻分布

Shader.TileMode tile: 渲染器平鋪模式*/

}

**6.2 復(fù)合控件 **

創(chuàng)建一個復(fù)核人控件可以很好的創(chuàng)建出具有重要功能的控件集合,這種方式經(jīng)常需要繼承一個合適的ViewGroup,再給他添加指定功能的控件,從而組成一個新的合適的控件,通過這種方式創(chuàng)建的控件,我們一般都會給他指定的一些屬性,讓他具有更強的擴展性,下面就以一個TopBar為例子,講解如何創(chuàng)建復(fù)合控件

定義屬性

我們需要給他定義一些屬性,這樣的話,我們需要在values下新建一個attrs.xml文件

我們在代碼中是可以用< declare-styleable >標簽去聲明一些屬性的,然后name相當于ID讓我們的類可以找到,,確定好之后,我們新建一個類,就叫TopBarView

package com.zc.demo;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.drawable.Drawable;

import android.util.AttributeSet;

import android.view.ViewGroup;

/**

* TopBar

* Created by Zc on 17/8/31.

*/

public class TopBarView extends ViewGroup {

private int mLeftTextColor;

private Drawable mLeftBackground;

private String mLeftText;

private int mRightTextColor;

private Drawable mRightBackgroup;

private String mRightText;

private float mTitleSize;

private int mTitleColor;

private String mTitle;

//帶參構(gòu)造方法

public TopBarView(Context context, AttributeSet attrs) {

super(context, attrs);

//通過這個方法,你可以從你的attrs.xml文件下讀取讀取到的值存儲在你的TypedArray

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);

//讀取出相應(yīng)的值設(shè)置屬性

mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor, 0);

mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftBackground);

mLeftText = ta.getString(R.styleable.TopBar_leftText);

mRightTextColor = ta.getColor(R.styleable.TopBar_rightTextColor, 0);

mRightBackgroup = ta.getDrawable(R.styleable.TopBar_rightBackground);

mRightText = ta.getString(R.styleable.TopBar_rightText);

mTitleSize = ta.getDimension(R.styleable.TopBar_titleTextSize, 10);

mTitleColor = ta.getColor(R.styleable.TopBar_titleTextColor, 0);

mTitle = ta.getString(R.styleable.TopBar_title);

//獲取完TypedArray的值之后,一般要調(diào)用recyle方法來避免重復(fù)創(chuàng)建時候的錯誤

ta.recycle();

}

6.3 重寫View來實現(xiàn)全新的控件

當我們Android原生的控件不滿足的話,我們可以繼承原來的控件修改,也可以組合起來使用,更加可以繼承View創(chuàng)建一個新的控件View

效果如圖所示

public class CircleProgressView extends View {

private int mMeasureHeigth;

private int mMeasureWidth;

private Paint mCirclePaint;

private float mCircleXY;

private float mRadius;

private Paint mArcPaint;

private RectF mArcRectF;

private float mSweepAngle;

private float mSweepValue = 66;

private Paint mTextPaint;

private String mShowText;

private float mShowTextSize;

public CircleProgressView(Context context, AttributeSet attrs,

int defStyleAttr) {

super(context, attrs, defStyleAttr);

}

public CircleProgressView(Context context, AttributeSet attrs) {

super(context, attrs);

}

public CircleProgressView(Context context) {

super(context);

}

@Override

protected void onMeasure(int widthMeasureSpec,

int heightMeasureSpec) {

mMeasureWidth = MeasureSpec.getSize(widthMeasureSpec);

mMeasureHeigth = MeasureSpec.getSize(heightMeasureSpec);

setMeasuredDimension(mMeasureWidth, mMeasureHeigth);

initView();

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

// 繪制圓

canvas.drawCircle(mCircleXY, mCircleXY, mRadius, mCirclePaint);

// 繪制弧線

canvas.drawArc(mArcRectF, 270, mSweepAngle, false, mArcPaint);

// 繪制文字

canvas.drawText(mShowText, 0, mShowText.length(),

mCircleXY, mCircleXY + (mShowTextSize / 4), mTextPaint);

}

private void initView() {

float length = 0;

if (mMeasureHeigth >= mMeasureWidth) {

length = mMeasureWidth;

} else {

length = mMeasureHeigth;

}

mCircleXY = length / 2;

mRadius = (float) (length * 0.5 / 2);

mCirclePaint = new Paint();

mCirclePaint.setAntiAlias(true);

mCirclePaint.setColor(getResources().getColor(

android.R.color.holo_blue_bright));

mArcRectF = new RectF(

(float) (length * 0.1),

(float) (length * 0.1),

(float) (length * 0.9),

(float) (length * 0.9));

mSweepAngle = (mSweepValue / 100f) * 360f;

mArcPaint = new Paint();

mArcPaint.setAntiAlias(true);

mArcPaint.setColor(getResources().getColor(

android.R.color.holo_blue_bright));

mArcPaint.setStrokeWidth((float) (length * 0.1));

mArcPaint.setStyle(Style.STROKE);

mShowText = setShowText();

mShowTextSize = setShowTextSize();

mTextPaint = new Paint();

mTextPaint.setTextSize(mShowTextSize);

mTextPaint.setTextAlign(Paint.Align.CENTER);

}

private float setShowTextSize() {

this.invalidate();

return 50;

}

private String setShowText() {

this.invalidate();

return "Panda_Program";

}

public void forceInvalidate() {

this.invalidate();

}

public void setSweepValue(float sweepValue) {

if (sweepValue != 0) {

mSweepValue = sweepValue;

} else {

mSweepValue = 25;

}

this.invalidate();

}

}

在來一個例子 效果如下所示

模擬音頻輸入

public class VolumeView extends View {

private int mWidth;

private int mRectWidth;

private int mRectHeight;

private Paint mPaint;

private int mRectCount;

private int offset = 5;

private double mRandom;

private LinearGradient mLinearGradient;

public VolumeView(Context context) {

super(context);

initView();

}

public VolumeView(Context context, AttributeSet attrs) {

super(context, attrs);

initView();

}

public VolumeView(Context context, AttributeSet attrs,

int defStyleAttr) {

super(context, attrs, defStyleAttr);

initView();

}

private void initView() {

mPaint = new Paint();

mPaint.setColor(Color.BLUE);

mPaint.setStyle(Paint.Style.FILL);

mRectCount = 12; //條形數(shù)量

}

//條形的漸變色

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

mWidth = getWidth();

mRectHeight = getHeight();

mRectWidth = (int) (mWidth * 0.6 / mRectCount);

mLinearGradient = new LinearGradient(

0,

0,

mRectWidth,

mRectHeight,

Color.YELLOW,

Color.BLUE,

Shader.TileMode.CLAMP);

mPaint.setShader(mLinearGradient);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

for (int i = 0; i < mRectCount; i++) {

mRandom = Math.random();

float currentHeight = (float) (mRectHeight * mRandom);

canvas.drawRect(

(float) (mWidth * 0.4 / 2 + mRectWidth * i + offset),

currentHeight,

(float) (mWidth * 0.4 / 2 + mRectWidth * (i + 1)),

mRectHeight,

mPaint);

}

//300毫秒重新繪制一次

postInvalidateDelayed(300);

}

}

7 自定義ViewGroup

這個管理子View的管理者,我們來定義一下,通常我們自定義ViewGroup是需要onMeasure()來測量的,然后重寫onLayout()來確定位置,重寫onTouchEvent()來相應(yīng)事件

接下來制作一個仿ScrollView的效果并且增加粘性事件。

public class MyScrollView extends ViewGroup {

private int mScreenHeight;

private Scroller mScroller;

private int mLastY;

private int mStart;

private int mEnd;

public MyScrollView(Context context) {

super(context);

initView(context);

}

public MyScrollView(Context context, AttributeSet attrs) {

super(context, attrs);

initView(context);

}

public MyScrollView(Context context, AttributeSet attrs,

int defStyleAttr) {

super(context, attrs, defStyleAttr);

initView(context);

}

private void initView(Context context) {

WindowManager wm = (WindowManager) context.getSystemService(

Context.WINDOW_SERVICE);

DisplayMetrics dm = new DisplayMetrics();

wm.getDefaultDisplay().getMetrics(dm);

mScreenHeight = dm.heightPixels;

mScroller = new Scroller(context);

}

@Override

protected void onLayout(boolean changed,

int l, int t, int r, int b) {

int childCount = getChildCount();

// 設(shè)置ViewGroup的高度

MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();

mlp.height = mScreenHeight * childCount;

setLayoutParams(mlp);

for (int i = 0; i < childCount; i++) {

View child = getChildAt(i);

if (child.getVisibility() != View.GONE) {

child.layout(l, i * mScreenHeight,

r, (i + 1) * mScreenHeight);

}

}

}

@Override

protected void onMeasure(int widthMeasureSpec,

int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int count = getChildCount();

for (int i = 0; i < count; ++i) {

View childView = getChildAt(i);

measureChild(childView,

widthMeasureSpec, heightMeasureSpec);

}

}

@Override

public boolean onTouchEvent(MotionEvent event) {

int y = (int) event.getY();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

mLastY = y;

mStart = getScrollY();

break;

case MotionEvent.ACTION_MOVE:

if (!mScroller.isFinished()) {

mScroller.abortAnimation();

}

int dy = mLastY - y;

if (getScrollY() < 0) {

dy = 0;

}

if (getScrollY() > getHeight() - mScreenHeight) {

dy = 0;

}

scrollBy(0, dy);

mLastY = y;

break;

case MotionEvent.ACTION_UP:

int dScrollY = checkAlignment();

if (dScrollY > 0) {

if (dScrollY < mScreenHeight / 3) {

mScroller.startScroll(

0, getScrollY(),

0, -dScrollY);

} else {

mScroller.startScroll(

0, getScrollY(),

0, mScreenHeight - dScrollY);

}

} else {

if (-dScrollY < mScreenHeight / 3) {

mScroller.startScroll(

0, getScrollY(),

0, -dScrollY);

} else {

mScroller.startScroll(

0, getScrollY(),

0, -mScreenHeight - dScrollY);

}

}

break;

}

postInvalidate();

return true;

}

private int checkAlignment() {

int mEnd = getScrollY();

boolean isUp = ((mEnd - mStart) > 0) ? true : false;

int lastPrev = mEnd % mScreenHeight;

int lastNext = mScreenHeight - lastPrev;

if (isUp) {

//向上的

return lastPrev;

} else {

return -lastNext;

}

}

@Override

public void computeScroll() {

super.computeScroll();

if (mScroller.computeScrollOffset()) {

scrollTo(0, mScroller.getCurrY());

postInvalidate();

}

}

}

8 事件攔截機制分析

這章講的是一個事件攔截機制的一些基本概念,,當Android系統(tǒng)撲捉到用戶的各種輸入事件之后,如何準確的傳遞給真正需要這個事件的控件尼?其實Android提供了一套非常完善的事件傳遞,處理機制,來幫助開發(fā)者完成準確的事件分配和處理要想了解攔截機制,我們首先要知道什么事觸摸事件,一般MotionEvent提供的手勢,我們常用的幾個DOWN,UP,MOVE什么的在MotionEvent中封裝了很多東西,比如獲取坐標點event.getX()和getRawX()獲取

一般ViewGroup我們需要重寫三個方法

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

return super.dispatchTouchEvent(ev);

}

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

return super.onInterceptTouchEvent(ev);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

return super.onTouchEvent(event);

}

而View則只要重寫兩個方法

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

return super.dispatchTouchEvent(ev);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

return super.onTouchEvent(event);

}

小結(jié)

至此空間架構(gòu)與自定義控件基礎(chǔ)內(nèi)容就完成了,日后碰見有趣的自定義控件,我會更新在我的博客上,歡迎瀏覽

總結(jié)

以上是生活随笔為你收集整理的自定义控件android.r,Android控件架构与自定义控件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。