目录
一、Android 的四大组件
- Activity: 包含用户界面的组件, 主要用于与用户交互
- Service: 后台运行的解决方案,不需要用户交互的长期运行任务
-
BroadcastReceiver: 系统级的发布-订阅机制
-
ContentProvider: 不同应用程序之间共享数据Intent: 各组件之间进行交互、传递意图(动作和数据), 用户的一切行为可以由多个组件无缝协同支持。eg:Tom: “ 我把上次我们聚会的照片发你 mail ”Jack: “ok”,心想: 照片把我拍的太丑了,我要P一下,再发到 ….以上操作需要以下动作:①在邮件app浏览图片②分享到图片编辑app③编辑修改图片④分享到社交app完成以上动作,Android和PC交互对比:PC: 应用之间无关联,通过桌面+鼠标拖拽进行交互;Android: 没有“桌面+鼠标”,“应用”之间直接交互( 基于组件开发 : 通过四大组件和 Intent 进行交互 )
二、UI的基石——Activity
1.创建Activity
- 创建XXActivity.java,继承Activity
- 在AndroidManifest.xml里面注册
- 编写布局xml
- 在onCreate里面setContent(xml)
2.跳转与关闭
使用startActivity()进行跳转,使用Intent传递数据;
3.Activity生命周期
- onSaveInstanceState和onRestoreInstanceState(保存之前状态)
4.Intent相关
- 显示Intent:明确了要指定启动的组件
表示从当前界面跳转至XmlActicity
- 隐示Intent:没有明确指定的组件,会把命中特定规则的组件都唤起然后选择

比如要打开百度首页,只设置网页和动作,手机下方弹出所有的浏览器供用户选择。
- startActivityForResult:启动一个新的Activity之后需要有返回值


三、基础UI组件
1.View和ViewGroup
- 常见控件View
TextView、Button、EditText、ImageView
- 常见布局ViewGroup
线性布局LinearLayout 、相对布局RelativeLayout 、帧布局FrameLayout

相对布局(相对于xx的位置)
帧布局 (写在越下面,则界面元素相对位于上面)
2.view的继承关系
3.屏幕适配
3.1 基本概念

3.2 相对布局
3.3 点九图——Nine Patch图
以.9.png为拓展名的png图片文件,可拉伸位图,适用于不定长内容背景(eg:聊天气泡)。


当前手机为xxhdpi,但是文件中最高密度只有xhdpi的图片,则从高到底查找,即xxhdpi中没有,则用xhdpi文件的图片。国内企业通常选择一个稍大密度的目录存放一份图片(减小apk包大小),原因:缩放不会失真,扩大可能会有一些失真。
四、事件传递
● 触摸事件(MotionEvent):ACTION_DOWN、ACTION_MOVE、ACTION_UP
● 事件分发(Dispatch):dispatchTouchEvent
● 事件拦截 (Intercept):onInterceptTouchEvent
eg:
没当一个触摸事件到来,都是从 上往下执行分发,从下往上执行消费事件,即从Activity往View分发,如果有拦截事件则停止往下分发,回传执行消费事件,没有消费成功(消费事件的bool值为false时)往上回传给上一层消费,如果到最高层的Activity也没有消费,则该事件就被抛弃。
五、高级UI组件
1.ListView
1.1 简介
一个显示可滚动项目的视图组件,系统使用Adapter(适配器)将列表项目插入列表,适配器从来源提取内容。在Android 5.0版本之后提供了RecycleView去替代ListView和GridView,提供了一种插拔式的体验,即模块化。
1.2 ListView复用机制
采用MVC模式, 模型(model)-视图(view)-控制器 (controller)
ps:引用自代码丶如风ActivityView其实就是在UI屏幕上可见的视图(onScreenView),也是与用户进行交互的View,那么这些View会通过RecycleBin直接存储到mActivityView数组当中,以便为了直接复用,那么当我们滑动ListView的时候,有些View被滑动到屏幕之外(offScreen) View,那么这些View就成为了ScrapView,也就是废弃的View,已经无法与用户进行交互了,这样在UI视图改变的时候就没有绘制这些无用视图的必要了。他将会被RecycleBin存储到mScrapView数组当中,但是没有被销毁掉,目的是为了二次复用,也就是间接复用。当新的View需要显示的时候,先判断mActivityView中是否存在,如果存在那么我们就可以从mActivityView数组当中直接取出复用,也就是直接复用,否则的话从mScrapView数组当中进行判断,如果存在,那么二次复用当前的视图,如果不存在,那么就需要inflate View了。
1.3 代码示例
①布局
②适配器的代码:
自定义一个适配器,继承自BaseAdapter,重写四个方法,getCount、getItem、getItemId、getView
public class ListBaseAdapter extends BaseAdapter {
private static final int NUM_LIST_ITEMS = 100;
// 数据集总个数
@Override
public int getCount() {
return NUM_LIST_ITEMS;
}
// 根据position获取数据对象
@Override
public Object getItem(int position) {
return null;
}
// 根据position获取数据对象的id
@Override
public long getItemId(int position) {
return 0;
}
// 对应position的item数据展示样式view
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
// 查看convertView是否为空,如果为空则重新创建,使用setTag方法实现保留复用
if (convertView == null) {
holder = new ViewHolder();
convertView = View.inflate(parent.getContext(), R.layout.number_list_item, null);
holder.listItemNumberView = (TextView) convertView.findViewById(R.id.tv_item_number);
holder.viewHolderIndex = (TextView) convertView.findViewById(R.id.tv_view_holder_instance);
convertView.setTag(holder);
} else {
// 复用机制,直接使用viewholder,无需每次都find id
holder = (ViewHolder) convertView.getTag();
}
// 更新item对应的数据值
holder.listItemNumberView.setText(String.valueOf(position));
holder.viewHolderIndex.setText(String.format("ViewHolder index: %s", position));
int backgroundColorForViewHolder = ColorUtils.
getViewHolderBackgroundColorFromInstance(convertView.getContext(), position % 10);
convertView.setBackgroundColor(backgroundColorForViewHolder);
return convertView;
}
private static class ViewHolder {
private TextView viewHolderIndex;
private TextView listItemNumberView;
}
}
③编写Activity,设置我们自己写的适配器。
public class ListViewActivity extends AppCompatActivity {
private Toast mToast;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listview);
ListView listView = (ListView) findViewById(R.id.list_numbers);
listView.setAdapter(new ListBaseAdapter());
listView.setDivider(null);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mToast != null) {
mToast.cancel();
}
String toastMessage = "Item #" + position + " clicked.";
mToast = Toast.makeText(ListViewActivity.this, toastMessage, Toast.LENGTH_LONG);
mToast.show();
}
});
}
}
注意: 在ListView中可以实现Click很简单,直接调用setOnItemClickListner,如上诉代码所示。
2 RecyclerView
2.1 RecyclerView简介
相比于ListView只能做上下滑动的视图,而RecyclerView可以灵活地使用包括线性布局(支持纵向、横向滑动)、网络布局、瀑布流。
2.2 RecyclerView代码例子
①布局
②编写适配器
核心的方法:onCreateViewHolder、onBindViewHolder、getItemCount(类似于ListView中的getCount)
在ListView中,需要我们手动去实现ViewHolder,而在RecyclerView中将ViewHolder的方法接口暴露出来。
如果没有ViewHolder则通过onCreateViewHolder创建,有则调用onBindViewHolder。在onBindViewHolder中通过我们自己写的bind函数设置相应的视图。
/**
* 适配器
*/
public class GreenAdapter extends RecyclerView.Adapter<GreenAdapter.NumberViewHolder> {
private static final String TAG = "GreenAdapter";
private int mNumberItems;
private final ListItemClickListener mOnClickListener;
private static int viewHolderCount;
public GreenAdapter(int numListItems, ListItemClickListener listener) {
mNumberItems = numListItems;
mOnClickListener = listener;
viewHolderCount = 0;
}
/*
* 一般会预留2~4个ViewHolder,off screen的数量由mCachedSize来决定
*
* The number of ViewHolders that have been created. Typically, you can figure out how many
* there should be by determining how many list items fit on your screen at once and add 2 to 4
* to that number. That isn't the exact formula, but will give you an idea of how many
* ViewHolders have been created to display any given RecyclerView.
*
* Here's some ASCII art to hopefully help you understand:
*
* ViewHolders on screen:
*
* *-----------------------------*
* | ViewHolder index: 0 |
* *-----------------------------*
* | ViewHolder index: 1 |
* *-----------------------------*
* | ViewHolder index: 2 |
* *-----------------------------*
* | ViewHolder index: 3 |
* *-----------------------------*
* | ViewHolder index: 4 |
* *-----------------------------*
* | ViewHolder index: 5 |
* *-----------------------------*
* | ViewHolder index: 6 |
* *-----------------------------*
* | ViewHolder index: 7 |
* *-----------------------------*
*
* Extra ViewHolders (off screen)
*
* *-----------------------------*
* | ViewHolder index: 8 |
* *-----------------------------*
* | ViewHolder index: 9 |
* *-----------------------------*
* | ViewHolder index: 10|
* *-----------------------------*
* | ViewHolder index: 11|
* *-----------------------------*
*
* index:12 from where?
*
* Total number of ViewHolders = 12
*
*
* 不做特殊处理:最多缓存多少个ViewHolder N(第一屏可见) + 2 mCachedSize + 5*itemType RecyclePool
*
* 找到position一致的viewholder才可以复用,新的位置由于position不一致,所以不能复用,重新创建新的
* 这也是为什么 mCachedViews一开始缓存的是0、1 所以 8、9、10需要被创建,
* 那为什么10 和 11也要被创建?
*
* 当view完全不可见的时候才会被缓存回收,这与item触发getViewForPosition不同,
* 当2完全被缓存的时候,实际上getViewForPosition已经触发到11了,此时RecyclePool有一个viewholder(可以直接被复用)
* 当12触发getViewForPosition的时候,由于RecyclePool里面有,所以直接复用这里的viewholder
* 问题?复用的viewholder到底是 0 1 2当中的哪一个?
*
*
* RecycleView 对比 ListView 最大的优势,缓存的设计,减少bindView的处理
*/
@NonNull
@Override
public NumberViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
Context context = viewGroup.getContext();
int layoutIdForListItem = R.layout.number_list_item;
LayoutInflater inflater = LayoutInflater.from(context);
boolean shouldAttachToParentImmediately = false;
View view = inflater.inflate(layoutIdForListItem, viewGroup, shouldAttachToParentImmediately);
NumberViewHolder viewHolder = new NumberViewHolder(view);
viewHolder.viewHolderIndex.setText("ViewHolder index: " + viewHolderCount);
int backgroundColorForViewHolder = ColorUtils
.getViewHolderBackgroundColorFromInstance(context, viewHolderCount);
viewHolder.itemView.setBackgroundColor(backgroundColorForViewHolder);
Log.d(TAG, "onCreateViewHolder: number of ViewHolders created: " + viewHolderCount);
viewHolderCount++;
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull NumberViewHolder numberViewHolder, int position) {
Log.d(TAG, "onBindViewHolder: #" + position);
numberViewHolder.bind(position);
}
@Override
public int getItemCount() {
return mNumberItems;
}
public class NumberViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private final TextView viewHolderIndex;
private final TextView listItemNumberView;
public NumberViewHolder(@NonNull View itemView) {
super(itemView);
listItemNumberView = (TextView) itemView.findViewById(R.id.tv_item_number);
viewHolderIndex = (TextView) itemView.findViewById(R.id.tv_view_holder_instance);
itemView.setOnClickListener(this);
}
public void bind(int position) {
listItemNumberView.setText(String.valueOf(position));
// viewHolderIndex.setText(String.format("ViewHolder index: %s", getAdapterPosition()));
// int backgroundColorForViewHolder = ColorUtils.
// getViewHolderBackgroundColorFromInstance(itemView.getContext(), getAdapterPosition() % 10);
// itemView.setBackgroundColor(backgroundColorForViewHolder);
}
@Override
public void onClick(View v) {
int clickedPosition = getAdapterPosition();
if (mOnClickListener != null) {
mOnClickListener.onListItemClick(clickedPosition);
}
}
}
// 自定义接口
public interface ListItemClickListener {
void onListItemClick(int clickedItemIndex);
}
}
③编写Activity
public class RecycleViewActivity extends AppCompatActivity implements GreenAdapter.ListItemClickListener {
private static final String TAG = "wangyi";
private static final int NUM_LIST_ITEMS = 100;
private GreenAdapter mAdapter;
private RecyclerView mNumbersListView;
private Toast mToast;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycleview);
mNumbersListView = findViewById(R.id.rv_numbers);
// LinearLayoutManager布局管理,定义布局样式
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mNumbersListView.setLayoutManager(layoutManager);
/*
* Use this setting to improve performance if you know that changes in content do not
* change the child layout size in the RecyclerView
*/
mNumbersListView.setHasFixedSize(true);
/*
* The GreenAdapter is responsible for displaying each item in the list.
*/
mAdapter = new GreenAdapter(NUM_LIST_ITEMS, this);
mNumbersListView.setAdapter(mAdapter);
mNumbersListView.addOnScrollListener(new RecyclerView.OnScrollListener() {
// 最后一个完全可见项的位置
private int lastCompletelyVisibleItemPosition;
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (visibleItemCount > 0 && lastCompletelyVisibleItemPosition >= totalItemCount - 1) {
Toast.makeText(RecycleViewActivity.this, "已滑动到底部!,触发loadMore", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
lastCompletelyVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
}
Log.d(TAG, "onScrolled: lastVisiblePosition=" + lastCompletelyVisibleItemPosition);
}
});
}
@Override
public void onListItemClick(int clickedItemIndex) {
Log.d(TAG, "onListItemClick: ");
if (mToast != null) {
mToast.cancel();
}
String toastMessage = "Item #" + clickedItemIndex + " clicked.";
mToast = Toast.makeText(this, toastMessage, Toast.LENGTH_LONG);
mToast.show();
}
}
注意:在RecyclerView中默认没有像ListView中的点击方法。做法如下:
- 在Activity中设置Adapter的时候会把我们自己定义的Listener传入。
- 在Adapter中自定义一个onListItemClick的接口
2.3 Adapter:
- ItemView 复用Google已经搞定,不用再setTag
-
ViewHolder的编写规范化

2.4 复用(三级缓存 + 自定义缓存):
