自定义View的 第一种形式继承现有的UI控件:实现特定功能,例如事件拦截,重新绘制。(继承某个控件,例如EditText,需要两个构造方法)
0:自定义View的步骤和使用:
继承View或者View的子类
声明构造方法
View(Context c) 这个构造方法在代码中创建控件的时候使用
View(Context ct,AttributeSet set)这个构造在布局xml文件中创建控件的时候自动的调用,如果自定义控件没有这个构造方法,就会抱错。报错地址:http://blog.csdn.net/rodulf/article/details/50915600,这个含有AttributeSet 的构造方法,应用于布局创建控件,这个AttribteSet attrs 就是XML里面的属性,通过这个属性传给控件,不然控件怎么知道高度时多少,宽度是多少,
1:继承已有的控件的方式
如果要拦截,就重写onInterceptTouchEvent
如果要重新绘制,那么就是重写onDraw()方法,onDraw方法是一个回调方法,当Android 要显示当前的控件到屏幕上的时候,就会回调这个方法,让控件自己把自己长什么样子画到屏幕上
canvas就是画布的意思,当控件在画布上面画完之后,最后由系统贴到屏幕上,记住是贴到屏幕上面的
如果上面的onDraw里面删除了super.onDraw(canvas)的话,是不会显示的,调整控件的显示:通过空间的onDraw 方法来修改,super.onDraw(canvas) 代表原有的空间显示方式;
canvas.drawArc可以用来画饼图
canvas.drawBitmap();可以切图,按照等比切,drawBitmap还可以用来做穿衣和试衣的软件 。
/!!! 记住了一定不能在onDraw方法里面进行对象的创建,这样非常影响性能。
2:
+++++++++++
1: 游戏,股票,涂鸦
2:办公统计,柱状统计。
3: 电子书
4:听力测试
像大神一样写代码
-----------------------------------------
提升的地方:-----------------------------
-----------------------------------------
内置的UI空间和布局无法满足需求的时候,就需要进行外观,操作都是自定义的一些控件,
这个时候就要进行自定义View
-----------------------------------------
自定义View 的方式:
1)继承现有的UI空间:实现特定功能
2)将多个空间组合,形成新的自定义View:瀑布流,Radio动态指示器
3)完全自定义回执:自己来话出来外观,自己实现事件
自定义View 的步骤和使用:
1:继承View 或者View 的子类
2:申明构造方法
3:View(Context c)这个构造在代码中创建空间的时候使用
4:View(Context ct,AttributeSet set)这个构造在布局xml 文件中创建空间的时候自动调用。
如果自定义空间没有博啊汗带有AttributeSet的参数的构造方法,就会报异常。
新建一个CusomerView1
创建一个包widget
新建一个java类SimpleView
public class SimpleView extends EditText {
/**
* 任何控件,只有一个参数的构造方法,应用于代码创建控件
* 例如 ImageView imageView = new ImageView(context);
* @param context
*/
public SimpleView(Context context) {
super(context);
}
/**
* 包含AttributeSet 的构造方法,应用于在布局文件中加载控件的情况。
* AttributeSet 实际上就是包含了布局中的XML指定的属性;通过这个属性传给空间。
* 这个方法在布局中创建空间的时候,是必须要调用和存在的,不然会抛出异常。
* @param context
* @param atts
*/
public SimpleView(Context context,AttributeSet atts){
super(context,atts);
}
}
main 的xml
<!--包含自定义空间或者第三方控件或者Android Support 中的空间
都可以通过类的全路劲来包含
-->
<com.kodulf.customerview1.widget.SimpleView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#EE3344"
android:hint="请输入您的内容"/>
修改:
public class SimpleView extends LinearLayout {
<com.kodulf.customerview1.widget.SimpleView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:text="自定义View 里面添加一个View"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</com.kodulf.customerview1.widget.SimpleView>
在修改
public class SimpleView extends LinearLayout {
<com.kodulf.customerview1.widget.SimpleView
android:layout_width="match_parent"
android:layout_height="_match_parent"
android:background="#EE3344"/>
早上第二课
----------------------------------------------
----------------------------------------------
新建NotePad java
shift+F6 修改名字
/**
* 自定义View 案例1:继承已有空间的方式,实现记事本输入界面:
*/
public class NotePadView extends EditText{
public NotePadView(Context context){
//super(context);
//通常,一个参数的构造,可以调用自身两个参数的
this(context,null);
}
public NotePadView(Context context, AttributeSet attrs){
super(context,attrs);
}
}-------------------------------------------------------
重写onDraw()方法
去韩国,按照某个图片去话,这个图片就是onDraw();
/**
* 这个方法是一个回调方法,当Android 要显示当前控件到屏幕上的时候
* 就会调用这个额方法啊,让控件自己把自己长什么样子画到屏幕上
*
* @param canvas Canvas 相当于一个画布,当空间在画布上面画完之后,最后由系统贴到屏幕上
* 记住是贴到屏幕上的。
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//TODO: 绘制内容:
}
如果上面的onDraw里面删除了super.onDraw(canvas)的话,是不会显示的
调整控件的显示:通过空间的onDraw 方法来修改
super.onDraw(canvas) 代表原有的空间显示方式;
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
drawArc()还可以画饼图,drawBitmap还可以切图,按照等比切,
//drawBitmap穿衣试衣的软件,还可以
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//android 代码当中,所有的和尺寸,坐标相关的单位,都是像素px
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Carvas 绘制的时候,都需要设置Paint,
/!!!!注意android 控件的onDraw方法,是进制创建任何对象的;
光标显示一次,创建一次,所以不允许创建任何对象。
设置成员变量Paint
/**
* 用于控制画线的样式;
* 通常不要再onDraw创建,
* 需要创建一个单独的初始化方法,
*/
private Paint linePaint;
/**
* 用于初始化绘制时使用的各种对象数据;
* @param context
* @param set
*/
private void init(Context context,AttributeSet set){
linePaint = new Paint();
linePaint.setColor(Color.RED);
}
然后再在构造方法调用;
public NotePadView(Context context, AttributeSet attrs){
super(context, attrs);
init(context,attrs);
}
更新:
protected void onDraw(Canvas canvas) {
//对于已有空间而言,super.onDraw 代表原有内容的显示;
super.onDraw(canvas);
//TODO: 绘制内容:只要使用Canvase的绘制,就可以实现显示了;
//Android 控件中,自身的左上角永远是(0,0)
//右下角 就是(width,height)坐标轴
//android 的整个屏幕,也是左上角(0,0)
//android 代码当中,所有的和尺寸,坐标相关的单位,都是像素px
//从(startX,startY)到(endX,endY)
//!!!!注意android 控件的onDraw方法,是进制创建任何对象的;
canvas.drawLine(
0,//startX
50,//startY
200,//endX
50,
linePaint);//endY
//drawArc()还可以画饼图,drawBitmap还可以切图,按照等比切,
//drawBitmap穿衣试衣的软件,还可以
}
更新:
以空间的宽度来画:
//继承已有空间:充分利用以后空间的方法和属性,完成功能
//1.获取控件的宽度,
int width =getWidth();
canvas.drawLine(0,50,width,50,linePaint);
更新:
//继承已有空间:充分利用以后空间的方法和属性,完成功能
//1.获取控件的宽度,
int width =getWidth();
//2.对于EditText子类而言,需要获取一行的文本的高度,
//因为可以设置textSize,通过 getLineHeight();来得到
int lineHeight = getLineHeight();
canvas.drawLine(0, lineHeight, width, lineHeight, linePaint);
这个时候会出现,会出现线在字体上面
解决方法如下:
android:background="#FFF"
//3. 在绘制的时候,需要考虑一个空间内部的padding 信息
// 默认的情况下EditText包含顶部Padding
空间绘制的时候的注意事项:
1:需要考虑空间自身的padding,padding 影响了计算坐标的位置
更新:
//1.获取控件的宽度,
int width =getWidth();
//2.对于EditText子类而言,需要获取一行的文本的高度,
//因为可以设置textSize,通过 getLineHeight();来得到
int lineHeight = getLineHeight();
//3. 在绘制的时候,需要考虑一个空间内部的padding 信息
// 默认的情况下EditText包含顶部Padding
//设置android:background="#FFF" 可以解决
int paddingTop = getPaddingTop();
canvas.drawLine(0, lineHeight+paddingTop, width, lineHeight+paddingTop, linePaint);
更新:
//4。根据行数来绘制线段,
//获取当当前输入框,实际内容的行数,
int lineCount = getLineCount();
for(int i=0;i<lineCount;i++){
canvas.drawLine(0,paddingTop+lineHeight*(i+1),width,paddingTop+lineHeight*(i+1),linePaint);
}
UPDATE:
int lineCount = getLineCount();
for(int i=0;i<lineCount;i++){
int lineY = paddingTop + lineHeight * (i + 1);
canvas.drawLine(0, lineY,width, lineY,linePaint);
}
更新:
//5. 获取高度,来第一次绘制的时候就绘制整个屏幕,需要减去paddingtop 和padding bottom
int height = getHeight();
//获取padding bottom 计算实际内容高度
int paddingBottom = getPaddingBottom();
int num = (height - paddingBottom - paddingTop)/lineHeight;
int lineCount = getLineCount();
lineCount = Math.max(lineCount,num);
for(int i=0;i<lineCount;i++){
int lineY = paddingTop + lineHeight * (i + 1);
canvas.drawLine(0, lineY,width, lineY,linePaint);
}
下午:
------------------------------------------------------------
------------------------------------------------------------
在onDraw方法,不允许创建对象:
在onDraw方法,里面打印Log。
这个方法什么也不动,它每隔0.5秒绘制一次。
和光标的闪烁一样的频率。
如果在这里面
new int[1024*1024]
移动的时候也会分配,所以这个会让程序非常非常慢。
所以不要再onDraw方法创建对象。
Android 自己的操作系统,正式的绘制的频率是16ms绘制一次。
每一个控件都有自己的绘制频率。
例如EditText 0.5s一次。它用在光标的闪烁。
--------------------------------------------------------------
--------------------------------------------------------------
自定义的属性
我们想要加入
lineColor="#0F0"
这个时候需要我们自己添加:
在values 文件夹,添加一个values 文件叫做attr.xml
btw:
format 的num 可以用来形容orientation 这样的属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--自定义属性的声明-->
<!--name 通常推荐是类名,这样好区别
可以认为是给某一个对应的空间声明属性
-->
<declare-styleable name="NotePadView">
<!--attr代表定义的属性名称,相当于NotePadView内部支持属性定义-->
<attr name="lineColor" format="color"/>
<!--定义一个名称为lineHeight的属性,内容是尺寸-->
<attr name="lineHeigth" format="dimension"/>
</declare-styleable>
</resources>
这个时候就可以添加 app:lineColor="#0F0"
通常命名空间的名称是app,是自动生成的,实际上就是xml命名空间;
在main的xml 里面自动导入了:
xmlns:app="http://schemas.android.com/apk/res-auto"
这个时候设置还没有完全的好。
还需要在代码中获取自定义属性;
更新init 如下:
private void init(Context context,AttributeSet set){
linePaint = new Paint();
//linePaint.setColor(Color.RED);
//TODO: 从AttributeSet获取属性配置,来设置横线的属性
int lineColor = Color.RED;
if(set!=null) {
//获取属性集合中的lineColor属性
//获取自定义属性操作步骤:
//1.先获取指定的,在attrs.xml中定义的那个属性集合declare-style
//代表的内容
//obtainStyledAttributes(); 获取自定义属性,需要AttributeSet才可以
TypedArray array = context.obtainStyledAttributes(
set,//包含了xml 中当前控件的所有属性 android:,app:这两种开头的
R.styleable.NotePadView//获取针对NotePadView 的所有属性。
);
//获取颜色属性,
//参数一:index,就是android中自定义属性的索引,
int color = array.getColor(
R.styleable.NotePadView_lineColor,//通过常量已经定义好了index了
Color.RED
);
linePaint.setColor(color);
//TypedArray 对象在使用完之后,必须要被回收
float dimension = array.getDimension(R.styleable.NotePadView_lineHeigth, 5.5f);
linePaint.setStrokeWidth(dimension);
array.recycle();
}
}
------------------------------------------------------------
----------------------------------------------------------------
组合控件,事件分发。
组合方式的自定义View
listView 就是一个组合布局
ViewPager 也是一个组合布局
AutoComplementTextView,Spinner 也都是的。
常见的第三方控件:
1:瀑布流,
2:拨号盘
3:侧滑菜单
4:Path按钮,
5:水纹进度
6:事件滚轮
---------------------------------------------------------------------
----------------------------------------------------------------------
继承ViewGroup 以及ViewGroup 的子类
新建一个java 文件AlphaIndicator
/**
* 通过组合的形式,实现字模选择的功能
* 采用组合控件的形式。
*/
public class AlphaIndicator extends LinearLayout {
public AlphaIndicator(Context context) {
//super(context); 这个也行,下面的也行
this(context,null);
}
public AlphaIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}
private void init(Context context,AttributeSet attrs){
//TODO:初始化各种内容和各种属性
//1.添加A-Z 26个字母
char ch;
for (int i = 0; i <26; i++) {
ch =(char)('A'+i);
TextView textView = new TextView(context);
textView.setText(Character.toString(ch));
//关于layoutParams,通过LayoutParams 可以给一个控件设置相应的布局属性,
//控件添加到哪一种布局中,就是用这个布局的LayoutParams的对象来设置。
//例如控件添加到LinearLayout ,那么个给这个空间设置的就是
//LinearLayout.LayoutParams 对应的就是android:layout_xxx
LinearLayout.LayoutParams lp = new LayoutParams(
//32,//像素单位的实际数值,或者是WRAP_CONTENT,MATCH_PARENT
ViewGroup.LayoutParams.MATCH_PARENT,
0,//0高度,因为使用权重,让控件填满布局,
1//weight 对应layout_weight="1"
);
textView.setLayoutParams(lp);//设置布局参数
//设置textSize 属性
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,20);
//添加进
addView(textView);
}
}
}
<com.kodulf.customerview1.widget.AlphaIndicator
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
></com.kodulf.customerview1.widget.AlphaIndicator>
更新完善:
//设置Android:gravity
textView.setGravity(Gravity.CENTER);
textView.setTextColor(Color.RED);
--------------------------------------------------------------
-----------------------------------------------------------------------
组合控件里面的事件处理
1:控件的触摸事件分为两种形式:
1)控件自身的onTouchEvent(...),控件自己来实现和处理触摸事件,是一种默认的处理
2) 给空间设置的setOnTouchListener();外部来设置触摸事件的处理【表情】可以覆盖onTouchEvent();
默认的处理;
2:通常对于自定义空间而言,可以采用onTouchEvent,外部将setOnTouchListener 留给外部代码;
3:触摸事件的类型:DOWN,MOVE,UP等,
4:ACTION_DOWN, 如果
//!!!!!如果ACTION_DOWN的状态,返回true 代表当前控件需要继续
//执行其他状态的监控;如果返回false,或者super调用,
//都不会再收到触摸事件了;
--------------------------------------------------------------------------------------
/**
* 空间自身处理触摸屏幕的事件
* @param event
* @return true 代表当前时间处理完成,芙蓉起不要再处理;false,代表芙蓉起还可以继续处理
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
//action 代表触摸的操作状态。
int action = event.getAction();
String type="";
switch (action){
case MotionEvent.ACTION_DOWN:
//所有的触摸屏幕操作都是从ACTION_DOWN开始的,也就是手指按下开始
//!!!!!如果ACTION_DOWN的状态,返回true 代表当前控件需要继续
//执行其他状态的监控;如果返回false,或者super调用,
//都不会再收到触摸事件了;
type="DOWN";
break;
case MotionEvent.ACTION_MOVE:
//手指移动
type="MOVE";
break;
case MotionEvent.ACTION_UP:
type="UP";
//手指离开屏幕
break;
}
Log.d("151222MY", type);
return super.onTouchEvent(event);
}
上面只会显示DOWN
//!!!!!如果ACTION_DOWN的状态,返回true 代表当前控件需要继续
//执行其他状态的监控;如果返回false,或者super调用,
//都不会再收到触摸事件了;
--------------------------------------------------------
更新:
/**
* 空间自身处理触摸屏幕的事件
* @param event
* @return true 代表当前时间处理完成,芙蓉起不要再处理;false,代表芙蓉起还可以继续处理
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
---》 boolean ret = false;
//action 代表触摸的操作状态。
int action = event.getAction();
String type="";
switch (action){
case MotionEvent.ACTION_DOWN:
//所有的触摸屏幕操作都是从ACTION_DOWN开始的,也就是手指按下开始
//!!!!!如果ACTION_DOWN的状态,返回true 代表当前控件需要继续
//执行其他状态的监控;如果返回false,或者super调用,
//都不会再收到触摸事件了;
type="DOWN";
---》 ret=true;
break;
case MotionEvent.ACTION_MOVE:
//手指移动
type="MOVE";
break;
case MotionEvent.ACTION_UP:
type="UP";
//手指离开屏幕
break;
}
Log.d("151222MY", type);
//return super.onTouchEvent(event);
---》 return ret;
}
------------------------------------------------------------------------------------------
/**
* 使用成员变量,进行上一次选中位置的保存,
* 便面多次选中线通的位置,
* 因为mover的动作会执行多次。
*/
private int currentPosition = -1;
/**
* 空间自身处理触摸屏幕的事件
* @param event
* @return true 代表当前时间处理完成,芙蓉起不要再处理;false,代表芙蓉起还可以继续处理
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean ret = false;
//action 代表触摸的操作状态。
int action = event.getAction();
//触摸事件包含x,y
float ex = event.getX();
float ey = event.getY();
//Log.d("151222MY","x:"+ex+" y:"+ey);
//TODO:事件相对于手机屏幕的x,y如何获取
String type="";
switch (action){
case MotionEvent.ACTION_DOWN:
//所有的触摸屏幕操作都是从ACTION_DOWN开始的,也就是手指按下开始
//!!!!!如果ACTION_DOWN的状态,返回true 代表当前控件需要继续
//执行其他状态的监控;如果返回false,或者super调用,
//都不会再收到触摸事件了;
type="DOWN";
//因为每次按下 是新的触摸事件刘晨给,那么续重位置清空,
currentPosition=-1;
ret=true;
break;
case MotionEvent.ACTION_MOVE:
//手指移动
type="MOVE";
int count = getChildCount();//获取内部的控件个数,根据空间个数,找到手机放在那个位置上了
int position =-1;
for(int i=0;i<count;i++){
View view = getChildAt(i);
// float viewX = view.getX();
// float viewY = view.getY();
float viewX = view.getLeft();
float viewY = view.getTop();
int viewRight = view.getRight();
int viewBottom = view.getBottom();
if(ex>=viewX&&ex<=viewRight){
if(ey>=viewY&&ey<=viewBottom){
//当前点中了,这个控件
position=i;
break;
}
}
}
if(position>-1&&position!=currentPosition){
// Toast.makeText(getContext(),"选中:"+position,Toast.LENGTH_SHORT).show();
Log.d("151222MY","选中:"+position);
currentPosition=position;
}
break;
case MotionEvent.ACTION_UP:
type="UP";
//手指离开屏幕
break;
}
//Log.d("151222MY", type);
//return super.onTouchEvent(event);
return ret;
}
-----------------------------------------------------------------
更新:
if(position>-1&&position!=currentPosition){
View v = getChildAt(position);
String alpha =(String)v.getTag();
// Toast.makeText(getContext(),"选中:"+position,Toast.LENGTH_SHORT).show();
//Log.d("151222MY","选中:"+position);
Log.d("151222MY","选中:"+alpha);
currentPosition=position;
}
更新 添加textView.setTag(ch);到下面的方法中。
private void init(Context context,AttributeSet attrs){
//TODO:初始化各种内容和各种属性
//1.添加A-Z 26个字母
char ch;
for (int i = 0; i <26; i++) {
ch =(char)('A'+i);
TextView textView = new TextView(context);
textView.setText(Character.toString(ch));
textView.setTag(ch);