- 参考郭霖文章
- http://blog.csdn.net/guolin_blog/article/details/12921889
- 1,LayoutInflater的内部实现方式,主要是pull解析布局文件,然后通过反射的方式生成控件对象,形成dom树结构;所以想要设置控件的layout_width 属性必须在该控件外嵌套一个布局才能生效,否则怎样改大小都不会生效的!
- 2,setContentView方法会给xml布局文件添加一个FrameLayout;
- 3,视图界面都是由两个部分组成:标题栏和内容栏,标题栏是系统自动给我们添加的,内容栏会自动添加一个根布局framelagout;
setContentView(R.layout.zidingyi_view);
//这里R.layout.button_view,后面是null,只有Button布局,没有父布局,所以layout_width属性怎样设置都是无效的,只能在布局中添加一个父布局才能生效,或者在代码中添加父布局及括号内的写法;
LayoutInflater lf = LayoutInflater.from(this);
View buttonView = lf.inflate(R.layout.button_view, null);
( View buttonView = lf.inflate(R.layout.button_view, new LinearLayout(this)); )//将null该为父布局!!
mLayout.addView(buttonView);
ViewParent viewParent = mLayout.getParent();
Log.d("gggggg==","这是"+viewParent);//gggggg==: 这是android.support.v7.widget.ContentFrameLayout@41fe9678
- 深入了解(onMeasure,onLayout,ondraw)
public class MyView extends ViewGroup {//这里是 ViewGroup(容器)
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
//这个方法是确定父视图MyView的大小!
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/*这里可以直接设置该MyView的大小super()改为 setMeasuredDimension(200, 200);尽量不要这么定义
* 需要注意的是,在setMeasuredDimension()方法调用之后,
* 我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,
* 以此之前调用这两个方法得到的值都会是0。
* 由此可见,视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,
* 而开发人员可以在XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板
* */
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(getChildCount() > 0){
View childView = getChildAt(0);//得到第一个子视图
measureChild(childView , widthMeasureSpec , heightMeasureSpec);
}
}
//这个方法是给视图惊醒布局,确定视图的位置!
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {//左上右下的4个坐标
if(getChildCount() > 0 ){
View childView =getChildAt(0);
//这里面的4个参数就代表这 左(20代表离左边20dp吧) 上 右(代表在onMeasure()得到的宽) 下(得到的高) 数值改变就会改变子视图在父视图MyView的位置
childView.layout( 20 , 0 , childView.getMeasuredWidth() , childView.getMeasuredHeight());
}
/*
* 首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,
* 而getWidth()方法要在layout()过程结束后才能获取到。
* 另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,
* 而getWidth()方法中的值则是通过视图右边的坐标 减去 左边的坐标计算出来的。
*
* 这里给子视图的layout()方法传入的四个参数分别是
* 0、0、childView.getMeasuredWidth()和childView.getMeasuredHeight(),
* 因此getWidth()方法得到的值就是childView.getMeasuredWidth() - 0 = childView.getMeasuredWidth() ,
* 所以此时getWidth()方法和getMeasuredWidth() 得到的值就是相同的
* */
}
- 下面就是布局文件
<?xml version="1.0" encoding="utf-8"?>
<com.demo.zhihudemo.test.MyView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/dddd_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/to_up"/>
//要知道上面只会显示一个布局,下面的都会舍弃!!!
<com.demo.zhihudemo.test.MyViewTwo
android:layout_width="200dp"
android:layout_height="200dp"/>
</com.demo.zhihudemo.test.MyView>
- 显示结果
- onDraw方法–就直接上代码了,郭神的博客可以去看
public class MyViewTwo extends View {//这是View!!!
private Paint mPaint;
public MyViewTwo(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//得到画笔实例,是使位图抗锯齿的标志
}
@Override
protected void onDraw(Canvas canvas) {//自己画图!!!
mPaint.setColor(Color.WHITE);//父视图的背景颜色
canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);//父视图的大小,形状rect是矩形!
mPaint.setColor(Color.BLUE);//这次设置的画笔颜色是为了下一步字体的设置
mPaint.setTextSize(20);//画笔的大小
// 第二参数是X轴,第三参数Y轴是总视图的一半的位置 - 100代表往上移动100dp(因为X轴箭头向右为正!Y轴箭头向下为正!)
canvas.drawText("MyViewTest", 10 ,
getHeight()/2-100 , mPaint);
}
}
- 布局文件信息—–借用了之前写的MyView的自定义View
<?xml version="1.0" encoding="utf-8"?>
<com.demo.zhihudemo.test.MyView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.demo.zhihudemo.test.MyViewTwo
android:layout_width="200dp"
android:layout_height="200dp"/>
</com.demo.zhihudemo.test.MyView>
- 显示结果–白色是之前设置的,蓝色是后来设置的!
- 视图状态
-
- enabled
表示当前视图是否可用。可以调用setEnable()方法来改变视图的可用状态,传入true表示可用,传入false表示不可用。它们之间最大的区别在于,不可用的视图是无法响应onTouch事件的。
- enabled
- focused
表示当前视图是否获得到焦点。通常情况下有两种方法可以让视图获得焦点,即通过键盘的上下左右键切换视图,以及调用requestFocus()方法。而现在的Android手机几乎都没有键盘了,因此基本上只可以使用requestFocus()这个办法来让视图获得焦点了。而requestFocus()方法也不能保证一定可以让视图获得焦点,它会有一个布尔值的返回值,如果返回true说明获得焦点成功,返回false说明获得焦点失败。一般只有视图在focusable和focusable in touch mode同时成立的情况下才能成功获取焦点,比如说EditText。 - window_focused
表示当前视图是否处于正在交互的窗口中,这个值由系统自动决定,应用程序不能进行改变。 - selected
表示当前视图是否处于选中状态。一个界面当中可以有多个视图处于选中状态,调用setSelected()方法能够改变视图的选中状态,传入true表示选中,传入false表示未选中。 pressed
表示当前视图是否处于按下状态。可以调用setPressed()方法来对这一状态进行改变,传入true表示按下,传入false表示未按下。通常情况下这个状态都是由系统自动赋值的,但开发者也可以自己调用这个方法来进行改变drawable代码文件 —- selector_color.xml —-
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/red" android:state_pressed="true"></item>
<item android:drawable="@color/yellow" android:state_focused="true"></item>
<item android:drawable="@color/pink"></item>
<!--第一个是按下的状态, 第二个是获得焦点的状态,第三个是正常状态-->
</selector>
<!--添加到布局文件中的背景色中-->
<Button
android:id="@+id/btn_test_color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/selector_color"/>
这里就不上图 ,自己试下,我的是在正常显示的时候是pink颜色,点击之后是red颜色,不太清楚怎么获得focused焦点的状态…..
视图重绘
- 调用视图的setVisibility()、setEnabled()、setSelected()等方法时都会导致视图重绘,而如果我们想要手动地强制让视图进行重绘,可以调用 invalidate() 方法来实现。
当然了,setVisibility()、setEnabled()、setSelected()等方法的内部其实也是通过调用 invalidate() 方法来实现的
另外需要注意的是,invalidate()方法虽然最终会调用到performTraversals()方法中,但这时measure和layout流程是不会重新执行的,因为视图没有强制重新测量的标志位,而且大小也没有发生过变化,所以这时只有draw流程可以得到执行。而如果你希望视图的绘制流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而应该调用requestLayout()了。这个方法中的流程比invalidate()方法要简单一些,但中心思想是差不多的。
最后是自定义控件—–: 自定义View的实现方式大概可以分为三种,自绘控件、组合控件、以及继承控件。
- 自绘View
//这个是自绘View的实例
public class MyViewMyself extends View implements View.OnClickListener{
private Paint mPaint;
private Rect mBounds;
private int mConut;
public MyViewMyself(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBounds = new Rect();
//这里记住需要有点击事件.....
setOnClickListener(this);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);//这个调用不电泳都一样的吧?父类只是一个空方法....
//第一步 先设置画笔颜色
mPaint.setColor(Color.YELLOW);
//第二步 用canvas画布 画出该View的大小,并添加画笔颜色来当做背景色
canvas.drawRect(0 , 0 ,getWidth(), getHeight(), mPaint);
//第三步 更改画笔颜色,和字体大小
mPaint.setColor(Color.RED);
mPaint.setTextSize(30);
//第四步 获取文字的宽度和高度
String text = String.valueOf(mConut);//数字装换
//参数一:文字;参数二三:开始的位置到结束的位置(0文字的长度;参数4:文字界限,及文字宽高组成的是一个矩形的..
mPaint.getTextBounds(text ,0 , text.length() , mBounds);
//得到文字宽高...
float textWidth = mBounds.width();
float textHeight = mBounds.height();
//第五步 用画布将文字写到具体的位置上!
//View的总宽一半减去文字长度一半,高度的一般+字体高度的一半,其实就是字体在View中间显示!!!额...
canvas.drawText(text , getWidth()/2 - textWidth/2, getHeight()/2 + textHeight/2 , mPaint);
}
//这里是点击事件,目的是点一次View改变一次mCount的数字,到达20点击从0开始...
@Override
public void onClick(View v) {
mConut++;
if(mConut > 20)
mConut = 0;
//这里调用该方法后,根据源码分析会回调到onDraw()方法进行重重绘!!
invalidate();//调用该方法后悔回调到onDraw方法执行操作
}
}
- 组合控件
//首先定义一个title.xml文件
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#c9f1ec">
<Button
android:id="@+id/btn_back"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_marginLeft="5dp"
android:layout_centerVertical="true"
android:text="返回"
android:textSize="18sp"
/>
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是标题"
android:layout_centerInParent="true"
android:textSize="20sp"
android:textColor="#0e0e0e"
/>
</RelativeLayout>
- 然后定义一个View 继承FrameLayout
public class MyViewZuHe extends FrameLayout {
private Button mBack;
private TextView mTitle;
public MyViewZuHe(Context context, AttributeSet attrs) {
super(context, attrs);
//传入布局,this是MyViewZuHe.this 是ViewGroup的子类
LayoutInflater.from(context).inflate(R.layout.title, this);
//找到相应的控件
mBack = (Button) findViewById(R.id.btn_back);
mTitle = (TextView) findViewById(R.id.tv_title);
//设置控件的点击事件
mBack.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Activity)getContext()).finish();//结束活动!
}
});
}
//设置title 和back的名字 及back的点击事件;注意是public!!!
public void setTitleText(String text){
mTitle.setText(text);
}
public void setBackText(String text){
mBack.setText(text);
}
public void setBackListener(OnClickListener listener){
mBack.setOnClickListener(listener);
}
}
- 然后再Activity的布局文件中添加该**组合控件**
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0334e6">
<com.demo.zhihudemo.test.MyViewZuHe
android:id="@+id/zu_he_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.demo.zhihudemo.test.MyViewZuHe>
图片
- 在Activity中调用!!
//得到组合View的实例,调用里面的public方法!
mZuHe = (MyViewZuHe) findViewById(R.id.zu_he_view);
mZuHe.setTitleText("改变标题");
mZuHe.setBackText("改变");
mZuHe.setBackListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext ,"改变点击事件",Toast.LENGTH_LONG).show();
}
});
更改后的图片
点击事件,会显示“改变点击事件”
- 继承控件…
- 首先准备一个删除按钮的布局
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/delete_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/delete_button"
>
</Button>
- 然后继承ListView ,
- 首先onTouch事件中,如果删除按钮显示了,则点击别的地方就移除删除按钮;如果没有显示就用GestureDetector来处理当前手势操作
public class MyListView extends ListView implements View.OnTouchListener ,GestureDetector.OnGestureListener
{
private GestureDetector mGestureDetector;//这里是手势探测器
private OnDeleteListener mListener;//自定义的一个删除监听
private ViewGroup mItemLayout;//这里是得到ItemView
private View mDeleteBtn;//删除按钮布局
private int mItemSelected;//判断当前选择的位置
private boolean isDeleteShowed;
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, this);
setOnTouchListener(this);
}
//设置删除监听,调用onDelete方法,根据位置索引来对数组集合进行操作!!
public void setDeleteListener(OnDeleteListener listener){
mListener = listener;
}
@Override
public boolean onDown(MotionEvent e) {
if(! isDeleteShowed ){
//返回该ListView(屏幕)的item的 position
mItemSelected = pointToPosition((int)e.getX() ,(int) e.getY());
}
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//判断X轴方向的速度大于Y轴方向是速度,即向左右快速滑动。。。显示 删除按钮
if(!isDeleteShowed && Math.abs(velocityX) > Math.abs(velocityY) ){
mDeleteBtn = LayoutInflater.from(getContext()).inflate(R.layout.delete_btn , null);
mDeleteBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//点击删除按钮后,移除该删除按钮,
mItemLayout.removeView(mDeleteBtn);
mDeleteBtn = null;
isDeleteShowed = false;
//这里通过回调,将位置传入,然后后面通过调用 位置 来进行删除操作,
// 没有这行代码也就没有位置索引,就不能进行删除操作了
mListener.onDelete(mItemSelected);
}
});
//得到ItemView
//getChildAt(int position)参数 为当前屏幕里面显示的item 从0开始 并不是adapter里面的位置
// 如果当前屏幕显示的list列表从 5--8 的数据 则
//getChildAt(0) 为在list中position为5的那个item
mItemLayout = (ViewGroup) getChildAt(mItemSelected - getFirstVisiblePosition() );
//下面是将 删除按钮 在ItemView的位置设置!
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);//右中位置
params.addRule(RelativeLayout.CENTER_VERTICAL);
mItemLayout.addView(mDeleteBtn , params);
isDeleteShowed = true;
}
return false;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if(isDeleteShowed){
//触摸不是删除按钮的地方,则移除显示在ItemView的该删除按钮
mItemLayout.removeView( mDeleteBtn );
mDeleteBtn = null;
isDeleteShowed = false;
return false;
}else {
return mGestureDetector.onTouchEvent(event);
}
}
public interface OnDeleteListener{
public void onDelete(int index);
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
}
- 后面就是创建Adapter ,布局只有一个TextView ,之后在Activit的布局中使用MyListView
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.demo.NotePad.test.MyListView
android:id="@+id/my_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.demo.NotePad.test.MyListView>
</RelativeLayout>
- 在Activity中进行操作
public class MyListViewActivity extends Activity {
private MyListView mListView ;
private MylvAdapter mAdapter;
private List<String> mStrings = new ArrayList<String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_listview);
initString();//添加数据
mListView = (MyListView) findViewById(R.id.my_list_view);
//这里通过删除监听中的位置索引来进行 删除操作!
mListView.setDeleteListener(new MyListView.OnDeleteListener() {
@Override
public void onDelete(int index) {
mStrings.remove(index);
mAdapter.notifyDataSetChanged();
}
});
mAdapter = new MylvAdapter(this, 0 , mStrings);
mListView.setAdapter(mAdapter);
}
private void initString() {
for (int i = 0; i <20 ; i++) {
mStrings.add("子列表第" + i +"项");
}
}
}