自定义View
布局,绘制,触摸反馈
View的绘制流程
1.根部:
Android framework 处理是从根节点开始,对布局进行measure和draw ,整个View树的绘制流程在ViewRoot.java类的performTraversals()函数展开
performTraversals:
1.是否需要重新计算视图大小measure,2.是否需要重新安置视图的位置layout,3.是否需要重新绘制draw
(measure测量)大小 -->(layout安排)位置 --> (draw绘制)内容
绘制顺序
- DecorView是根布局
- 然后按照树形图遍历,父view要measure自己之后再去遍历子view
measure 过程传递的两个类
- ViewGroup.LayoutParams(View自身的布局参数)
用来指定高度和宽度,getLayoutParams()会得到父view的layoutParams - MeasureSpecs类(父视图对自视图的测量要求)
测量和尺寸要求,有三个模式
UNSPECIFIED,没有约束
EXACTLY 确定尺寸
AT_MOST 父控件所给的最大尺寸,通常自定义
2.绘制流程
2.1 measure流程
- view的 measure方法
如果只是高度宽度的话,就需要super.onMeasure
如果完全自定义,不需要super
记得set
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
- measure(int,int) 调用onMeasure(int,int),但是measure是final类,所以子类必须复写onMeasure。
onMeasure:
setMeasuredDimension():设置view的宽,高
getDefaultSize():返回测量后的大小,或者mMinWidth和Drawable最小宽度的最大值
- ViewGrop的 measure方法
ViewGrop没有onMeasure()方法,但是定义了measureChildren
measureChildren:遍历所有children,获得子元素的属性 - LinearLayout的measure流程
LinearLayout 是 ViewGrop的子类,他去实现ViewGrop没有实现的onMeasure,
mTotalLength 存储垂直高度,
wrap_content:计算所有元素,求和
match_parent:和view的测量一样 - View的layout流程
传进来四个坐标点,坐标是对父布局来说的,并不是绝对做坐标
setFrame:设置四个顶点,然后执行onLayout
public void layout(int l, int t, int r, int b) {
...省略
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...省略
}
onLayout: layoutVertical(l, t, r, b);或者 layoutHorizontal(l, t, r, b);
setChildFrame,遍历子布局
2.2 layout流程
2.3 draw流程
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
/*
// 把 onDraw() 换成了 dispatchDraw()
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
... // 绘制斑点,覆盖在原有的图片上
}
*/
-
View.draw(Canvas canvas)
ViewGrop没有复写,需要View.draw,但是自定义view一般是用onDraw进行绘制,也可以先进行super.draw进行系统的draw绘制,在进行自定义的onDraw绘制
canvas的绘制:
canvas.drawCircle(3,3,5,p);//画一个圆,(坐标,半径,颜料)Paint:颜色风格都可以,绘制非常关键
canvas.drawBitmap();
canvas.drawPath();
canvas.drawRect();矩形
canvas.drawText(); -
View.onDraw()
默认空实现自定义需要自己复写方法,自己绘制 -
dispachDraw
viewGrop对子视图的绘制,自定义的ViewGrop不应该对dispachDraw进行复写 -
drawChild(cavans,this,drawingTime)
调用了 View 的child.draw(canvas, this,drawingTime)方法, -
invalidate
重绘draw树
dispatchDraw()–>drawChild()–>view.draw()
- draw六个步骤
1.若有背景则绘制
2.保存canvas层
3.绘制自身内容
4.如果有子元素则绘制子元素
5.绘制效果
6.绘制装饰品(scrollbars)
二 自定义View
https://blog.csdn.net/u010216743/article/details/77744597
https://blog.csdn.net/u010216743/article/details/77744602
1.自定义View需要具备的知识点
-
View坐标系
-
Android的角度与弧度
-
View绘制机制
一个Activity里最外层包含PhoneWindow,而PhoneWindow里包含DecorView,然后DecorView里包含有TitleView和ContentView,对于ContentView,我们立马就会联想到setContentView()方法有木有,其实这个方法就是设置ContentView的布局 -
View事件分发
-
Android动画机制
-
Canvas类
-
Path类
2.View绘制机制
View绘制大致可以分为三个流程,分别是measure(测量),layout(布局),draw(绘制),这三者的顺序就是measure(测量)->layout(布局)->draw(绘制)。
- measure: 判断是否需要重新计算View的大小,需要的话则计算;测量View的宽和高。
- layout: 判断是否需要重新计算View的位置,需要的话则计算;
- draw: 判断是否需要重新绘制View,需要的话则重绘制。
3.三星云的自定义View
- 自定义圆角。继承自LinearLayout,在ondraw方法中进行了P版本判断
- 自定义搜索结果的内容,继承自textView。修改了setText方法,用的 SpannableString方法控制搜索结果着色处理。
- BottomSelectFolderActivity,的布局是自己写的ShrinkHelper,自己只是完善了下滑动等圆角显现操作。
有对点击焦点的获取,通过ontTouchEvent,传到ShrinkHelper进行处理- getRawX():获取点击事件距离整个屏幕左边距离,即绝对坐标
- getRawY():获取点击事件距离整个屏幕顶边的的距离,即绝对坐标
三 ListView RecyclerView
1.ListView
1.1 用法
listData 是一个list,加载到getApplicationContext这个上下文的 r.layout.xxx布局下
listAdapter = new ListAdapter(getApplicationContext(), listview(r.layout.xxx), listData);
listview.setAdapter(listAdapter);
重点
继承重写 BaseAdapter 类
自定义 ViewHolder 和 convertView 一起完成复用优化工作
1.2 布局设置
addHeaderView()方法:主要是向listView的头部添加布局
addFooterView()方法:主要是向listView的底部添加布局
1.3 优化
static class ViewHolder{
TextView textView;
ImageView imageView;
Button button;
}
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(mContext);
ViewHolder holder = null;
if (convertView == null) {
convertView = inflater.inflate(R.layout.item, null);
holder = new ViewHolder();
holder.button = (Button) convertView.findViewById(R.id.button);
holder.textView = (TextView) convertView.findViewById(R.id.textView);
holder.imageView = (ImageView) convertView.findViewById(R.id.imageView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.imageView.setImageResource(R.mipmap.ic_launcher);
holder.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("click","button");
}
});
holder.textView.setText(data[position]);
return convertView;
}
-
1.3.1 ViewHolder机制
使用ViewHolder,不要在getView方法中写findViewById方法,因为getView方法会执行很多遍,这样也可以节省时间,节约内存。 -
1.3.2 convertView重用机制
在Adapter中的getView方法中使用ConvertView,即ConvertView的复用,不需要每次都inflate一个View出来,这样既浪费时间,又浪费内存。 -
1.3.3 三级缓冲/滑动监听事件
-
1.3.4 使用分页加载,讲真实际开发中,ListView的数据肯定不止几百条,成千上万条数据你不可能一次性加载出来,所以这里需要用到分页加载,一次加载几条或者十几条,但是如果数据量很大的话,像qq,微信这种,如果顺利加载到最后面的话,那么你的list中也会有几万甚至几十万的数据,这样可能也会导致OOM,所以你的数据集List中也不能有那么多数据,所以每加载一页的时候你可以覆盖前一页的数据。
-
1.3.5 如果数据当中有图片的话,使用第三方库来加载(也就是缓存),如果你的能力强大到能自己维护的话,那也不是不可以。
-
1.3.6 当你手指在滑动列表的时候,尽可能的不加载图片,这样的话滑动就会更加流畅。
2.RecyclerView
2.1 实现方式:
- 继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder
- 设置布局管理器,控制布局效果
2.2 RecyclerView的布局管理器
- 线性布局管理器(LinearLayoutManager)
- 网格布局管理器(GridLayoutManager)
- 瀑布流布局管理器(StaggeredLayouManager)
2.3 RecyclerView的适配器
// 第一步:继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder
public class AuthorRecyclerAdapter extends RecyclerView.Adapter<AuthorRecyclerAdapter.AuthorViewHolder> {
...
@Override
public AuthorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
...
return viewHolder;
}
@Override
public void onBindViewHolder(AuthorViewHolder holder, int position) {
...
}
@Override
public int getItemCount() {
if (mData == null) {
return 0;
}
return mData.size();
}
class AuthorViewHolder extends RecyclerView.ViewHolder {
...
public AuthorViewHolder(View itemView) {
super(itemView);
...
}
}
}
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerAdapter = new AuthorRecyclerAdapter(mData);
// 第二步:设置布局管理器,控制布局效果
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(RecyclerDemoActivity.this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(linearLayoutManager);
mRecyclerView.setAdapter(mRecyclerAdapter);
2.4 优点
- RecyclerView封装了viewholder的回收复用(listView也有),也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。
- 提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还StaggeredGridLayoutManager等),也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现GridView的效果等多种效果。你想控制Item的分隔线,可以通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去抒写代码。
- 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。
2.5 ListView&RecyclerView区别
ListView只支持列表,支持set空页面
RecyclerView支持三中布局,网格,列表,瀑布,不支持set空页面
2.6 优化
结合三星云的,图片和视频的缩略图的加载分析
三星云的Recent的不同区域的Adapter,只是通过设置四个(today,yesterday,7,30,90),这几个Adapter的clear和 set来放入到display中,然后 再把display放入到 driveRecyclerAdapter中,最后recyclerView.set(driveRecyclerAdapter)
ViewStub
动态加载视图,惰性加载,节约内存,提高性能,只加载一次
绘制的时候不会绘制到view树中,代码执行inflate之后才会被添加到视图中,是一个宽高都为0的View,默认Gone,需要SetVisibility或者inflate函数加载设置才能显现,达到延迟效果,提高加载速度
适用范围:网络请求页面失败
第一次加载会把viewStub从父布局中移除,parent.addView()把我们的view加载到父布局Linearlayout中
第二次加载会报错,以为已经移除了,所以不包含viewStub的parent,可以直接findViewById
动画
属性动画
缓速,监听器反馈
运用属性动画的api
imageView.setTranslateX(500),向右移动500