Android群英传学习——第三章、Android控件架构与自定义控件详解
来源:互联网 发布:亚历克斯·张伯伦 知乎 编辑:程序博客网 时间:2024/06/07 11:16
自定义控件我有跟着几个大神做的成果做一些,但是一直没有仔细的完整的学一遍理论,所以这章要好好学一下。
这一章的内容如下:
——Android控件架构
——View的测量与绘制
——ViewGroup的测量与绘制
——自定义控件的三种方式
——事件的拦截机制
一、Android控件架构
如下图展示了View视图树,在控制树的顶部是一个ViewParent对象,是整个树的控制核心,所有交互管理事件都由它来统一调度和分配。ViewGroup控件作为父控件可以包含多个View控件,并管理其包含的View控件。通常在Activity中使用的findViewById()方法,就是在控制树中以树的深度优先遍历来查找对应元素。
每个Activity都包含一个Window对象,Android中通常用PhoneWindow来实现。PhoneWindow将一个DecorView设置为整个应用窗口的根View,它封装了一些窗口操作的通用方法,在显示上,它将屏幕分为两部分:TitleView和ContentView。如下图:
setContentView()即加载ContentView部分的布局。如果用户通过设置requestWindowFeature(Window.FEATURE_NO_TITLE)来设置全屏显示,就一定要放在setContentView()方法之前才能生效。
在代码中,当程序在onCreate()方法中调用setContentView()方法后,ActivityManagerService会回调onResume()方法,此时系统才会把整个DecorView添加到PhoneWindow中,并让其显示出来,从而最终完成界面绘制。
二、View的测量
Android系统提供了一个强大的类——MeasureSpec类来帮助我们测量View。它是一个32位的int值,高2位为测量的模式,低30位为测量的大小。测量的模式可以为以下三种
EXACTLY——精确值模式:当控件的宽高属性指定为具体数值时使用AT_MOST——最大值模式:当控件的宽高属性指定为自适应时使用UNSPECIFIED——不精确模式:它不指定其大小测量模式,通常在绘制自定义View时才使用。
在自定义控件时,若指定了控件具体宽高值或者是match_parent属性,则使用EXACTLY模式,使用指定的specSize即可;如果指定的是wrap_content属性,即AT_MOST模式,必须重写onMeasure()方法来指定大小,通常作法是取出我们指定的大小与specSize中最小的一个来作为最后的测量值。代码如下:
public class MainActivity extends View { public MainActivity(Context context) { super(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec)); } private int measureHeight(int heightMeasureSpec) { int result = 0; int specMode = MeasureSpec.getMode(heightMeasureSpec); int specSize = MeasureSpec.getSize(heightMeasureSpec); if (specMode == MeasureSpec.EXACTLY){ result = specSize; }else{ result = 200; if(specMode == MeasureSpec.AT_MOST){ result = Math.min(result,result); } } return result; } private int measureWidth(int widthMeasureSpec) { int result = 0; int specMode = MeasureSpec.getMode(widthMeasureSpec); int specSize = MeasureSpec.getSize(widthMeasureSpec); if (specMode == MeasureSpec.EXACTLY){ result = specSize; }else{ result = 200; if(specMode == MeasureSpec.AT_MOST){ result = Math.min(result,result); } } return result; }}
三、View的绘制
测量好一个View后,通常需要通过继承View并重写它的onDraw()方法来完成绘图。onDraw()中有一个参数,就是Canvas对象,这个对象就像创建了一个画板,使用Paint就可以在上面作画了。创建Cavas对象代码:
Canvas canvas = new Canvas(bitmap);
传进去的bitmap与Canvas画布紧紧联系,这个过程称为装载画布,这个bitmap用来存储绘制在Canvas上的像素信息,调用所有的Canvas.drawXXX方法都发生在这个bitmap上。
canvas.drawBitmap(bitmap1,0,0,null);
//绘制直线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.drawBirmap(Bitmap bitmap, float left, float top, Paint paint);
四、ViewGroup的测量与绘制
ViewGroup会管理其子View,其中一个管理项目就是负责子View的显示大小。当ViewGroup指定为wrap_content时,ViewGroup会对子View遍历,从子View的大小来确定自己的大小。如果ViewGroup有指定的值则按值设定自己的大小。
子View测量完后,ViewGroup会遍历子View的Layout方法来指定其具体显示的位置,从而决定其布局位置。
自定义ViewGroup时,通常会重写onLayout()方法来控制其子View显示位置的逻辑。
ViewGroup通常不需绘制。但是它会使用dispatchDraw()方法来绘制其子View,其过程是通过遍历所以子View,并调用子View的绘制方法来完成绘制工作。
五、自定义View
心心念念想学的一部分。。它的作用就不用多说了,直接快点开始学把~
在View中比较重要的回调方法
1、onFinishInflate()
//从XML加载组件后回调 @Override protected void onFinishInflate() { // TODO Auto-generated method stub super.onFinishInflate(); }
2、onSizeChanged()
//组件大小改变时回调 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // TODO Auto-generated method stub super.onSizeChanged(w, h, oldw, oldh); }
3、onMeasure()
// 回调该方法进行测量 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
4、onLayout()
// 回调该方法来确定显示的位置 @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // TODO Auto-generated method stub super.onLayout(changed, left, top, right, bottom); }
5、onTouchEvent()
// 监听到触摸时间时回调 @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub return super.onTouchEvent(event); }
6、onDraw()
// 绘图 @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); }
创建自定义View的时候,并不需要重写所有的方法。通常有一下三种方法来实现自定义的控件:
对现有控件进行拓展通过组合来实现新的控件重写View来实现全新的控件
1、对现有控件进行拓展
这种方法就是在原生控件的基础上进行拓展,增加新的功能、修改显示的UI等。
第一个例子:让TextView的背景更加丰富
TextView使用onDraw()方法绘制要显示的文字,继承了系统的TextView后,一定要重写其onDraw()方法才能修改TextView展示的 效果。其实程序调用super.onDraw()方法来实现原生控件的功能,但是在调用super.onDraw()方法前和之后,我们可以实现自己的逻辑,即在系统绘制文字前后,完成自己的操作。如下所示:
@Override protected void onDraw(Canvas canvas) { //在回调父类之前,实现自己的逻辑,对textview来说就是绘制文本内容前 super.onDraw(canvas); //在回调父类之后,实现自己的逻辑,对textview来说就是绘制文本内容后 }
然后我们分析要实现这样的效果,需要绘制两个不同大小的矩形,形成一个重叠效果。那我们就要先初始化两个画笔:
//实例化画笔1 paint1 = new Paint(); //设置颜色 paint1.setColor(getResources().getColor(android.R.color.holo_blue_light)); //设置style paint1.setStyle(Paint.Style.FILL); //实例化另一个画笔 paint2 = new Paint(); paint2.setColor(Color.YELLOW); paint2.setStyle(Paint.Style.FILL);
然后开始绘制
//绘制外层 canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint1); //绘制内层 canvas.drawRect(10, 10, getMeasuredWidth() - 10, getMeasuredHeight() - 10, paint2); canvas.save(); //绘制文字前平移10像素 canvas.translate(10, 0); //父类完成方法 super.onDraw(canvas); canvas.restore();
第二个例子:实现一个动态的文字闪动效果
想要实现这一效果,可以充分利用Android中Paint对象的Shader渲染器。通过设置一个不断变化的LinearGradient,并使用带有该属性的Paint对象来绘制要显示的文字。首先我们要在onSizeChanged()方法中完成一些初始化操作。
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mViewWidth == 0) { mViewWidth = getMeasuredWidth(); if (mViewWidth > 0) { //获取画笔对象 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(); } } }
其中最关键的就是使用getPaint()方法获取当前绘制TextView的Paint对象,并给这个Paint对象设置原声TextView没有的LinearGradient属性。最后在onDraw()方法中,通过矩阵的方法来不断平移渐变效果,从而产生动态闪动效果:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (matrix != null) { mTranslate += mViewWidth + 5; if (mTranslate > 2 * mViewWidth / 5) { mTranslate = -mViewWidth; } matrix.setTranslate(mTranslate, 0); mLinearGradient.setLocalMatrix(matrix); //每隔100毫秒闪动一下 postInvalidateDelayed(100); } }}
下面放上完整的效果和代码:
布局代码
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.example.administrator.myview.TextViewTestOne android:id="@+id/tv_01" android:layout_width="match_parent" android:layout_height="80dp" android:text="Android" android:textSize="25sp"/> <com.example.administrator.myview.TextViewTestTwo android:id="@+id/tv_02" android:layout_marginTop="30dp" android:layout_width="match_parent" android:layout_height="80dp" android:text="Android" android:textSize="25sp"/>
TextViewTestOne.java
public class TextViewTestOne extends TextView { private Paint paint1, paint2; public TextViewTestOne(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { //实例化画笔1 paint1 = new Paint(); //设置颜色 paint1.setColor(getResources().getColor(android.R.color.holo_blue_light)); //设置style paint1.setStyle(Paint.Style.FILL); //同上 paint2 = new Paint(); paint2.setColor(Color.YELLOW); paint2.setStyle(Paint.Style.FILL); } @Override protected void onDraw(Canvas canvas) { //绘制外层 canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint1); //绘制内层 canvas.drawRect(10, 10, getMeasuredWidth() - 10, getMeasuredHeight() - 10, paint2); canvas.save(); //绘制文字前平移10像素 canvas.translate(10, 0); //父类完成方法 super.onDraw(canvas); canvas.restore(); }}
TextViewTestTwo.java
public class TextViewTestTwo extends TextView { private LinearGradient mLinearGradient; private Matrix mGradientMatrix; private Paint mPaint; int mTranslate; int mViewWidth; public TextViewTestTwo(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if(mViewWidth == 0){ mViewWidth = getMeasuredWidth(); if(mViewWidth>0){ mPaint = getPaint(); mLinearGradient = new LinearGradient(0,0,mViewWidth,0, new int[]{ Color.BLUE,0xffffff,Color.BLUE},null,Shader.TileMode.CLAMP ); mPaint.setShader(mLinearGradient); mGradientMatrix = new Matrix(); } } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(mGradientMatrix!=null){ mTranslate += mViewWidth/5; if(mTranslate>2*mViewWidth){ mTranslate = -mViewWidth; } mGradientMatrix.setTranslate(mTranslate,0); mLinearGradient.setLocalMatrix(mGradientMatrix); postInvalidateDelayed(100); } }}
MainActivity.java
public class MainActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextViewTestOne tv_01 = (TextViewTestOne) findViewById(R.id.tv_01); TextViewTestTwo tv_02 = (TextViewTestTwo) findViewById(R.id.tv_02); }}
2、创建复合控件
这种方式通常需要继承一个合适的ViewGroup,再给它添加指定功能的控件,从而组合成新的复合控件。通过这种方式创建的控件,我们一般会给他指定一些可配置的属性,使其具有更强的拓展性。以下是一个TopBar示例。
1)定义属性
在res资源目录下创建一个attrs.xml的属性定义文件,然后通过如下代码定义相应的属性即可。
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="TopBar"> <attr name="title" format="string" /> <attr name="titleTextSize" format="dimension" /> <attr name="titleTextColor" format="color" /> <attr name="leftTextColor" format="color" /> <attr name="leftBackground" format="reference|color" /> <attr name="leftText" format="string" /> <attr name="rightTextColor" format="color" /> <attr name="rightBackground" format="reference|color" /> <attr name="rightText" format="string" /> </declare-styleable></resources>
确定号属性后,就可以创建一个自定义控件TopBar,并让他继承ViewGroup,为了简单这里继承了RelativeLayout。在构造方法中通过如下代码来获取在xml布局文件中自定义的属性:
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);
系统提供了TypedArray这样的数据机构来获取自定义属性集,通过它的对象的getString(),getColor()等方法,就可以获取这些定义的属性值,代码如下:
//通过这个方法,你可以从你的attrs.xml文件下读取读取到的值存储在你的TypedArray TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar); //读取出相应的值设置属性 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的值之后,一般要调用recycle方法来避免重复创建时候的错误 ta.recycle();
2)组合控件
接下来,就要开始组合基础控件。创建左边的按钮,中间的标题栏和右边的按钮,设置他们的一些属性,然后设置各个控件在ViewGroup中的布局,最后用addView()方法将这三个控件加入到定义的TopBar模板中。
mLeftButton = new Button(context); mRightButton = new Button(context); mTitleView = new TextView(context); //为创建的元素赋值 mLeftButton.setTextColor(mLeftTextColor); mLeftButton.setBackground(mLeftBackground); mLeftButton.setText(mLeftText); mRightButton.setTextColor(mRightTextColor); mRightButton.setBackground(mRightBackgroup); mRightButton.setText(mRightText); mTitleView.setText(mTitle); mTitleView.setTextColor(mTitleColor); mTitleView.setTextSize(mTitleSize); mTitleView.setGravity(Gravity.CENTER); //为组件元素设置相应的布局元素 mLeftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE); //添加到ViewGroup addView(mLeftButton,mLeftParams); mRightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE); addView(mRightButton,mRightParams); mTitleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE); addView(mTitleView,mTitleParams);
定义接口——接下来创建一个接口,实现按钮的点击回调
//定义接口//接口对象,实现回调机制,在回调方法中//通过映射的接口对象调用接口中的方法//而不用去考虑如何实现,具体的实现由调用者去创建public interface topbarClickListener { //左按钮点击事件 void leftClick(); //右按钮点击事件 void rightClick();}
暴露接口给调用者——在模板方法中,为左右按钮增加点击事件
//按钮的点击事件,不需要具体的实现 //只需调用接口的方法,回调的时候,会有具体的实现 mLeftButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mListener.leftClick(); } }); mRightButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mListener.rightClick(); } }); //暴露一个方法给调用者来注册接口回调 //通过接口来获得回调者对接口方法的实现 public void setOnTopbarClickListener(topbarClickListener mListener){ this.mListener = mListener; }
实现接口回调——在调用这的代码中,要实现这样一个接口,并完成接口中的方法
mTopbar = (TopBar) findViewById(R.id.mTopbar); mTopbar.setOnTopbarClickListener(new topbarClickListener() { @Override public void leftClick() { Toast.makeText(MainActivity.this, "点击左侧按钮", Toast.LENGTH_SHORT).show(); } @Override public void rightClick() { Toast.makeText(MainActivity.this, "点击右侧按钮", Toast.LENGTH_SHORT).show(); } });
为了简单,只显示了两个Toast来区分不同按钮的点击事件
3)引用UI模板
最后一步,自然是在需要使用的地方引用UI模板,在引用前,需要指定引用第三方控件的名字空间。
例如
xmlns:android="http://schemas.android.com/apk/res/android"
这行代码就是在指定引用的名字空间xmlns,即xml namespace。这里指定了名字空间为“android”,因此在接下来使用系统属性时,才可以使用“android:”来引用Android的系统属性。
如果使用自定义属性,就要创建自己的名字空间:
<?xml version="1.0" encoding="utf-8"?><com.example.administrator.topbartest.TopBar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="40dp" custom:leftBackground="#128" custom:leftText="Back" custom:leftTextColor="#FFFFFF" custom:rightBackground="#128" custom:rightText="More" custom:rightTextColor="#FFFFFF" custom:title="自定义标题" custom:titleTextColor="#000" custom:titleTextSize="15sp"></com.example.administrator.topbartest.TopBar>
通过如上所示代码,我们就可以在其他的布局中,直接通过标签来引用这个UI模板View。
下面放上完整的代码
attrs.xml
<?xml version="1.0" encoding="utf-8"?><resources> <!--声明使用自定义属性,通过name属性来确定引用的名字--> <declare-styleable name="TopBar"> <attr name="title" format="string" /> <attr name="titleTextSize" format="dimension" /> <attr name="titleTextColor" format="color" /> <attr name="leftTextColor" format="color" /> <attr name="leftBackground" format="reference|color" /> <attr name="leftText" format="string" /> <attr name="rightTextColor" format="color" /> <attr name="rightBackground" format="reference|color" /> <attr name="rightText" format="string" /> </declare-styleable></resources>
topbarClickListener.java
//定义接口//接口对象,实现回调机制,在回调方法中//通过映射的接口对象调用接口中的方法//而不用去考虑如何实现,具体的实现由调用者去创建public interface topbarClickListener { //左按钮点击事件 void leftClick(); //右按钮点击事件 void rightClick();}
TopBar.java
public class TopBar extends RelativeLayout { 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; private Button mLeftButton,mRightButton; private TextView mTitleView; private LayoutParams mLeftParams,mRightParams,mTitleParams; private topbarClickListener mListener; //带参构造方法 public TopBar(Context context, AttributeSet attrs) { super(context, attrs); //通过这个方法,你可以从你的attrs.xml文件下读取读取到的值存储在你的TypedArray TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar); //读取出相应的值设置属性 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的值之后,一般要调用recycle方法来避免重复创建时候的错误 ta.recycle(); mLeftButton = new Button(context); //按钮的点击事件,不需要具体的实现 //只需调用接口的方法,回调的时候,会有具体的实现 mLeftButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mListener.leftClick(); } }); mRightButton = new Button(context); mRightButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mListener.rightClick(); } }); mTitleView = new TextView(context); //为创建的元素赋值 mLeftButton.setTextColor(mLeftTextColor); mLeftButton.setBackground(mLeftBackground); mLeftButton.setText(mLeftText); mRightButton.setTextColor(mRightTextColor); mRightButton.setBackground(mRightBackgroup); mRightButton.setText(mRightText); mTitleView.setText(mTitle); mTitleView.setTextColor(mTitleColor); mTitleView.setTextSize(mTitleSize); mTitleView.setGravity(Gravity.CENTER); //为组件元素设置相应的布局元素 mLeftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE); //添加到ViewGroup addView(mLeftButton,mLeftParams); mRightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE); addView(mRightButton,mRightParams); mTitleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE); addView(mTitleView,mTitleParams); } //暴露一个方法给调用者来注册接口回调 //通过接口来获得回调者对接口方法的实现 public void setOnTopbarClickListener(topbarClickListener mListener){ this.mListener = mListener; }}
topbar.xml
<?xml version="1.0" encoding="utf-8"?><com.example.administrator.topbartest.TopBar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="40dp" custom:leftBackground="#128" custom:leftText="Back" custom:leftTextColor="#FFFFFF" custom:rightBackground="#128" custom:rightText="More" custom:rightTextColor="#FFFFFF" custom:title="自定义标题" custom:titleTextColor="#000" custom:titleTextSize="15sp"></com.example.administrator.topbartest.TopBar>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/topbar" android:id="@+id/mTopbar"/></LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity { private TopBar mTopbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTopbar = (TopBar) findViewById(R.id.mTopbar); mTopbar.setOnTopbarClickListener(new topbarClickListener() { @Override public void leftClick() { Toast.makeText(MainActivity.this, "点击左侧按钮", Toast.LENGTH_SHORT).show(); } @Override public void rightClick() { Toast.makeText(MainActivity.this, "点击右侧按钮", Toast.LENGTH_SHORT).show(); } }); }}
3、重写View来实现全新的控件
例子一:比例图
我们来分析一下这个图是怎么画出来的。首先这个自定义View可以分为三部分,分别是中间的圆形,中间显示的文字和外圈的弧线。
下面直接看完整代码:
CircleView.java
public class CircleView extends View { //圆的长度 private int mCircleXY; //屏幕高宽 private int w,h; //圆的半径 private float mRadius; //圆的画笔 private Paint mCirclePaint; //弧线的画笔 private Paint mArcPaint; //文本画笔 private Paint mTextPaint; //需要显示的文字 private String mShowText = "hahahaha"; //文字大小 private int mTextSize = 50; //圆心扫描的弧度 private int mSweepAngle = 270; public CircleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); //获取屏幕高宽 WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); w = wm.getDefaultDisplay().getWidth(); h = wm.getDefaultDisplay().getHeight(); init(); } private void init() { mCircleXY = w/2; mRadius = (float)(w * 0.5 / 2); mCirclePaint = new Paint(); mCirclePaint.setColor(Color.BLUE); mArcPaint = new Paint(); //设置线宽 mArcPaint.setStrokeWidth(100); //设置空心 mArcPaint.setStyle(Paint.Style.STROKE); //设置颜色 mArcPaint.setColor(Color.BLUE); mTextPaint = new Paint(); mTextPaint.setColor(Color.WHITE); mTextPaint.setTextSize(mTextSize); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //绘制矩形 RectF mArcRectF = new RectF((float)(w * 0.1),(float)(w * 0.1),(float)(w * 0.9),(float)(w * 0.9)); //绘制圆 canvas.drawCircle(mCircleXY,mCircleXY,mRadius,mCirclePaint); //绘制弧线 canvas.drawArc(mArcRectF,270,mSweepAngle,false,mArcPaint); //绘制文本 canvas.drawText(mShowText,0,mShowText.length(),mCircleXY-mTextSize * 2 ,mCircleXY+mTextSize/2,mTextPaint); } //设置一个对外的弧度方法 public void setSweepValues(int sweepValues){ if(sweepValues != 0){ mSweepAngle = sweepValues; }else { //如果没有,我们默认设置 mSweepAngle = 30; } //刷新 invalidate(); }}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.administrator.circledemo.CircleView android:id="@+id/cv" android:layout_width="match_parent" android:layout_height="match_parent" /></LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CircleView circleView = (CircleView) findViewById(R.id.cv); circleView.setSweepValues(180);}
}
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CircleView circleView = (CircleView) findViewById(R.id.cv); circleView.setSweepValues(180); }}
例子二、音频条形图
直接看代码吧
YinPinRectView.java
public class YinPinRectView extends View { private Paint mPaint; //定义矩形数量 int mRectCount = 20; //定义矩形宽度和高度 private int mRectWidth ,mRectHeight; //设置偏移量 private int offset = 5; //定义当前矩形高度 private float currentHeight; //设置一个随机数 private double mRandom; private LinearGradient mLinearGradient; //屏幕宽高 private int w; public YinPinRectView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); w = wm.getDefaultDisplay().getWidth(); init(); } private void init() { mPaint = new Paint(); //设置线宽 mPaint.setStrokeWidth(30); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for(int i = 0;i<mRectCount;i++){ currentHeight = getRectHeight(); canvas.drawRect((float)(w * 0.05 + mRectWidth * i + offset),currentHeight, (float)(w * 0.05 + mRectWidth * (i+1) ),mRectHeight,mPaint); } postInvalidateDelayed(300); } //随即改变矩形的高 private float getRectHeight() { mRandom = Math.random(); float currentHeight = (float) (mRectHeight * mRandom); return currentHeight; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); w = getWidth(); mRectHeight = getHeight(); mRectWidth = (int)(w /mRectCount);//计算矩形的宽 //渐变效果 mLinearGradient = new LinearGradient(0,0,mRectWidth,mRectCount,Color.YELLOW,Color.BLUE, Shader.TileMode.CLAMP); mPaint.setShader(mLinearGradient); }}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.administrator.yinpinrectdemo.YinPinRectView android:id="@+id/yp" android:layout_width="match_parent" android:layout_height="match_parent" /></LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); YinPinRectView yp = (YinPinRectView) findViewById(R.id.yp); }}
五、事件拦截机制分析
http://blog.csdn.net/chunqiuwei/article/details/41084921
- Android群英传学习——第三章、Android控件架构与自定义控件详解
- Android群英传知识点回顾——第三章:Android控件架构与自定义控件详解
- 《Android群英传》阅读笔记——第三章:Android控件架构与自定义控件详解
- Android群英传学习-第三章:控件架构与自定义控件详解
- Android群英传学习——控件架构与自定义控件
- Android群英传 第三章-控件架构与自定义控件
- Android群英传笔记——第三章:Android控件架构与自定义控件讲解
- Android群英传笔记——第三章:Android控件架构与自定义控件讲解
- Android群英传读书笔记 第三章 Android控件架构与自定义控件详解
- Android群英传第三章笔记·Android控件架构与自定义控件详解
- 第三章Android 控件架构与 自定义控件详解(Android群英传)
- Android群英传——第三章Android控件架构
- 《Android群英传》读书笔记(1)第三章:Android控件与自定义控件详解
- Android群英传笔记-第3章 Android控件架构与自定义控件详解
- Android群英传读书笔记第三章(Android控件架构与自定义View)
- Android群英传之Android控件架构与自定义控件
- 阅读徐宜生《Android群英传》的笔记——第3章 Android控件架构与自定义控件详解(3.1-3.5)
- 阅读徐宜生《Android群英传》的笔记——第3章 Android控件架构与自定义控件详解(3.6-3.8)
- 框架中的部分源码替代写法
- 第五次模拟题(DNA)
- iOS-Block的使用
- Java大数据分析图的十大错误,你占了几个?
- Realize A+B , And Thinking of recursion
- Android群英传学习——第三章、Android控件架构与自定义控件详解
- 二叉查找树(BST) | 平衡二叉查找树(AVL) | 红黑树(RBT)
- JS传后台中文乱码问题
- iOS开发之---block的使用(详解)
- vue饿了么学习-第六篇(mock数据)
- 《算法》第四版第一章以及java核心技术卷I基础部分知识点
- double vs long double
- maven 快照 更新策略
- TL-WR941N V2 漏洞