Android基础02——安卓基础编程
Activity
创建
- 创建XXActivity.java,继承AppCompatActivity
- 在AndroidManifest.xml中注册,写法形如
<activity android:name="chapter.android.aweme.ss.com.chapter2.xmlparser.XmlActivity" />
- 编写布局xml,放在res/layout目录下
- 在XXActivity.java类中重写onCreate方法,并用setContentView(xml)展示相应的布局,形如
setContentView(R.layout.activity_main);
跳转与关闭
每个Activity相当于一个页面,应用的页面之间可以进行跳转。使用startActivity()方法进行跳转。在跳转时,必须传给该方法一个Intent(意图)。意图至少要指明当前上下文和跳转的页面Activity类,也可以增加其他额外的数据。最简单的意图形如new Intent(this, XmlActivity.class)
,传给startActivity()后可以跳转到XmlActivity。
intent包含四个重要的内容:
- Action 值为字符串,通常是系统中已经定义的常用行为
- Data 通常是URI格式的操作数
- Category 指定当前Action被执行的环境,默认为CATEGORY_DEFAULT
- Extras 一些补充的信息传递,通常用Bundle进行封装后传递
在定义intent的时候可以对这四个信息进行set,而跳转的目标界面也可以拿到随之传来的这四个内容,提供一定的服务。
此外,intent还有显式和隐式之分:
- 显式intent明确指定了要跳转到哪一个Activity
- 隐式intent不明确指明,而是填好action、catagory、data等信息,然后交给系统,系统去寻找匹配这个intent的所有界面(或者应用)。一个典型的例子就是应用唤出浏览器,就可以通过隐式intent预埋的方式实现。
如果要通过隐式intent跳转到另一个Activity,则必须在AndroidManifest.xml文件中给目标Activity注册一个intent-filter,并定义好这个Activity匹配的action和category,方便系统进行匹配。
隐式的intent往往可以在布局文件中直接绑定到Button上,例如:
1 <Button
2 android:id="@+id/btn_implicit_preview"
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content"
5 android:onClick="openBrowser"
6 android:text="隐式intent预埋(打开浏览器)" />
1 /**
2 * 隐式intent预埋
3 *
4 * @param view
5 */
6 public void openBrowser(View view) {
7 Intent it = new Intent();
8 it.setAction(Intent.ACTION_VIEW);
9 it.setData(Uri.parse("http://www.baidu.com"));
10 startActivity(it);
11 }
关闭时,调用finish()退出Activity。在真正使用时,返回键退出当前页面的时候也会调用这个方法。可以对该方法进行重写。
生命周期
可以在XXActivity.java中重写这7个方法来在适当的实际完成一些额外的动作。
退出重进或者被系统杀掉重启都会导致Activity对象被重新构建,重新初始化,之前的数据会丢失。有一个方法可以让之前的数据能够在重新构建时被加入,看起来就像Activity的状态被保存了下来。重写onSaveInstanceState()和onRestoreInstanceState()方法:
1 @Override
2 protected void onSaveInstanceState(Bundle outState) {
3 super.onSaveInstanceState(outState);
4 logAndAppend(ON_SAVE_INSTANCE_STATE);
5 String content = mLifecycleDisplay.getText().toString();//当前已有的log 提取出来
6 outState.putString(LIFECYCLE_CALLBACKS_TEXT_KEY, content); //把内容存储起来
7 }
8
9 @Override
10 protected void onRestoreInstanceState(Bundle savedInstanceState) {
11 super.onRestoreInstanceState(savedInstanceState);
12 logAndAppend(ON_RESTORE_INSTANCE_STATE);
13 }
onSaveInstanceState方法在Destroy之前执行,把当前的状态进行保存。然后下次重新创建之前会先调用onRestoreInstanceState方法把数据恢复成之前的模样。
基础UI组件
UI组件分为两类:View组件单元和ViewGroup组织组件的单元。两者的关系类似树中的叶节点和连接叶节点的父节点一样。
View
- TextView 文本框
- EditText 输入框
- Button 按钮
- ImageView 图片
- …
需要注意的是,不仅仅按钮能够通过点击和用户交互,文本框、图片等很多组件也可以设置点击的监听器,对用户点击的动作做出反应。
ViewGroup
- LinearLayout 线性布局。可以设置为水平或者垂直的。
- RelativeLayout 相对布局。其中每个元素的位置可以指定为相对于其他元素的定位。
- FrameLayout 帧布局。每个组件之间可以相互覆盖的布局。
下图表示了诸多View和ViewGroup的继承关系。
一个布局文件的最外层应当是一个ViewGroup。
屏幕适配
wrap_content、match_parent、weight、gravity这些关键字的属性可以将组件的大小、位置自动适应内容或者外层布局。
minWidth、minHeight 可以约束一个组件的最小尺寸。
Nine-Patch点九图可以依据内容自动拉伸,而且不影响视觉效果。
事件传递
触摸事件对应MotionEvent
类,三种事件类型:
ACTION_DOWN
,手指触碰屏幕时发生的事件ACTION_MOVE
,手指在屏幕上滑动时发生的事件ACTION_UP
,手指离开屏幕时发生的事件
这三种事件发生时,第一个接收到事件的会是当前的Activity,然后是页面布局上最外层的ViewGroup,接着是下层的ViewGroup或者View,层层嵌套。每一层都可以自己决定当有事件传来时如何处理这个事件。
处理事件有三个动作:
- dispatch分发,用dispatchTouchEvent(MotionEvent event)重写
- intercept拦截,用onInterceptTouchEvent(MotionEvent event)重写
- consume消费,用onTouchEvent(MotionEvent event)重写
首先要决定如何分发这个事件,然后分发给下层的过程中可以拦截,如果不分发给下层则会自己消费掉。下图即为正常情况下的事件分发逻辑:
消费即意味着事件传递的终点。super表示类似return super.dispatchTouchEvent(ev);
这样的情况。ViewGroup的super默认都是返回false的。
也可以参考下面这张图:
高级UI组件之列表
ListView
是一个显示可滚动项目的视图组件。需要配合使用Adapter将内容插入列表中。Adapter负责将每一个列表元素的视图样式和数据结合起来,填入ListView中去。
ListView每次会去找Adapter请求需要的View,Adapter内部将数据和item的布局结合后返回给ListView这个Item的View,这样ListView就可以显示每一个Item了。
ListView本身具有缓存复用的机制,可以防止一次加载过多的item导致卡顿。每次ListView只会加载稍多于一个屏幕能够显示的item数量,滑动屏幕时会将移出屏幕的item回收并加入新出现的item,大大提高了加载效率。不过这些都是ListView内部实现,我们使用者不用太在意。
重点是Adapter。有四种Adapter可供我们使用:
- ArrayAdapter:简单、易用的 Adapter,用于将数组数据作为数据源绑定到列表项中。支持泛型操作
- SimpleAdapter:相比 ArrayAdapter 来说,功能比较强大,可以将数据源的数据一一的绑定到 item 中的 view 中。
- CursorAdapter:用于绑定游标(直接从数据库取出数据)作为列表项的数据源,和数据库有关系,不常用。
- BaseAdapter:这个是我们在实际开发中经常用到的,我们需要继承 BaseAdapter 来自定义我们自己的适配器
其中ArrayAdapter和SimpleAdapter都直接使用就可,一般不需要重新定义。但是如果这两种Adapter不能满足使用的要求,需要继承BaseAdapter类来实现自己的Adapter。必须重定义的方法有四个:
1// 继承 BaseAdapter 必须要实现它的 4 个方法
2class MyAdapter extends BaseAdapter{
3
4 // 返回适配器中所代表的数据集合的条数
5 // 会首先执行这个方法(连续执行好几次),如果是 0 则后面的方法就不会执行了
6 @Override
7 public int getCount() {
8 return 0;
9 }
10
11 // 返回数据集合中指定索引 position 对应的数据项
12 // 手动调用才会执行
13 @Override
14 public Object getItem(int position) {
15 return null;
16 }
17 // 返回列表中与指定索引对应的行 id
18 // 手动调用才会执行
19 @Override
20 public long getItemId(int position) {
21 return 0;
22 }
23 // 返回指定索引对应的数据的视图,会多次调用
24 @Override
25 public View getView(int position, View convertView, ViewGroup parent) {
26 return null;
27 }
28}
最核心的方法还是getView方法,因为ListView正是通过这个方法拿到每一个item的。观察这个方法中有一个convertView,如果ListView调用时传入的convertView不为null,说明ListView腾出了一个itemview的地方可以供我们复用,这个时候就要充分利用复用的机制来提高效率了,不需要再重新new一个view,而是可以直接拿这个convertView修改一下里面的数据重新传回去即可。最佳的实践方法是借助ViewHolder进行复用:
1class ViewHolder{
2 TextView tv;
3 ImageView iv;
4 Button bt;
5}
6@Override
7public View getView(int position,View convertView,ViewGroup parent){
8 ViewHolder viewHolder;
9 if(convertView == null){
10 convertView = mInflater.inflater(R.layout.item,null);
11 viewHolder = new ViewHolder();
12 viewHolder.tv = convertView.findViewById(R.id.tv);
13 viewHolder.iv = convertView.findViewById(R.id.iv);
14 viewHolder.bt = convertView.findViewById(R.id.bt);
15 // 将 viewHolder 绑定到 convertView 中实现复用
16 convertView.setTag(viewHolder);
17 }else{// 如果convertView不为空说明可以复用,简单修改数据即可,无需重新创建视图
18 viewHolder = (ViewHolder)convertView.getTag();
19 }
20 viewHolder.iv.set.....
21 .....各种设置
22 return convertView;
23}
另外一个小tips就是不要在滑动的时候加载图片,否则可能会卡顿。
这部分内容参考博客ListView详细介绍与使用,里面介绍十分详细有用,很有参考价值。
RecycleView
android support-v7包 中新增的一个ListView升级版控件,对列表展示进行了更好的优化处理。
首先,RecycleView可以进行列表排列调整,只需要在Activity中setLayoutManager即可,参考如下:
1mNumbersListView = findViewById(R.id.rv_numbers);
2LinearLayoutManager layoutManager = new LinearLayoutManager(this);
3layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
4mNumbersListView.setLayoutManager(layoutManager);
5
6// 或者可以写成
7mRecyclerView.setLayoutManager(new
8 StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.VERTICAL));// 流式布局
9
10mRecycleView.setLayoutManager(new LinearLayoutManager(getContext()));// 普通的按列排列
然后重点就是RecycleView的Adapter写法。先上一个例子:
1public class GreenAdapter extends RecyclerView.Adapter<GreenAdapter.NumberViewHolder> {
2
3 private static final String TAG = "GreenAdapter";
4 private int mNumberItems;
5 private final ListItemClickListener mOnClickListener;
6 private static int viewHolderCount;
7
8 public GreenAdapter(int numListItems, ListItemClickListener listener) {
9 mNumberItems = numListItems;
10 mOnClickListener = listener;
11 viewHolderCount = 0;
12 }
13
14 // 必须重定义的方法onCreateViewHolder,核心方法
15 // 作用:在需要的时候创建新的ViewHolder。通常发生在列表刚开始展示那会儿,缓存里没有Holder,需要创建。下拉列表的时候,也可能缓存中Holder不够用,需要补充Holder
16 // 注意:这个方法仅仅负责创建,不负责填入Holder中View内的数据
17 @NonNull
18 @Override
19 public NumberViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
20 // 创建
21 Context context = viewGroup.getContext();
22 int layoutIdForListItem = R.layout.number_list_item;
23 LayoutInflater inflater = LayoutInflater.from(context);
24 boolean shouldAttachToParentImmediately = false;
25 View view = inflater.inflate(layoutIdForListItem, viewGroup, shouldAttachToParentImmediately);
26 NumberViewHolder viewHolder = new NumberViewHolder(view);
27
28 // 初始化
29 viewHolder.viewHolderIndex.setText("ViewHolder index: " + viewHolderCount);
30 int backgroundColorForViewHolder = ColorUtils
31 .getViewHolderBackgroundColorFromInstance(context, viewHolderCount);
32 viewHolder.itemView.setBackgroundColor(backgroundColorForViewHolder);
33
34 Log.d(TAG, "onCreateViewHolder: number of ViewHolders created: " + viewHolderCount);
35 viewHolderCount++;
36 return viewHolder;
37 }
38
39 // 必须重定义的核心方法,负责将缓存中的Holder绑定新数据,从而实现复用。
40 @Override
41 public void onBindViewHolder(@NonNull NumberViewHolder numberViewHolder, int position) {
42 Log.d(TAG, "onBindViewHolder: #" + position);
43 numberViewHolder.bind(position);
44 }
45
46 // 必须重定义
47 @Override
48 public int getItemCount() {
49 return mNumberItems;
50 }
51
52 // 必须定义Holder,Google将ListView中的好习惯直接规范化,核心组件
53 public class NumberViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
54
55 private final TextView viewHolderIndex;
56 private final TextView listItemNumberView;
57
58 // 构造时,和一个视图绑定起来,以后只会更换视图内的数据展示。
59 public NumberViewHolder(@NonNull View itemView) {
60 super(itemView);
61 listItemNumberView = (TextView) itemView.findViewById(R.id.tv_item_number);
62 viewHolderIndex = (TextView) itemView.findViewById(R.id.tv_view_holder_instance);
63 itemView.setOnClickListener(this); // 通过这种方法将点击事件传出来
64 }
65
66 // 负责更新与之绑定的视图中的数据,这里可以在onBindViewHolder中调用
67 public void bind(int position) {
68 listItemNumberView.setText(String.valueOf(position));
69 }
70
71 // 将点击事件传给外面的Activity。这里的监听者一般是外面的Activity
72 @Override
73 public void onClick(View v) {
74 int clickedPosition = getAdapterPosition();
75 if (mOnClickListener != null) {
76 mOnClickListener.onListItemClick(clickedPosition);
77 }
78 }
79 }
80
81 // 如果外面的Activity想要监听列表元素的点击事件,就要实现这个接口
82 public interface ListItemClickListener {
83 void onListItemClick(int clickedItemIndex);
84 }
85}
总结起来,至少要自己实现三个方法和一个内部类:
- 核心方法onCreateViewHolder,需要时创建Holder,系统会调用
- 核心方法onBindViewHolder,更换Holder绑定的View内部的数据展示,系统自动调用
- getItemCount,系统调用或者自己调用得到列表数量总数
- 核心组件Holder,必须继承RecyclerView.ViewHolder
如果需要监听点击事件,还需要一个接口和在Holder中帮助View传递给监听者(Activity)。
另外,RecycleView还可以借助ItemAnimator实现一些小动画,更加好看。
在内部的实现中,RecycleView采用的是三级缓存,比ListView更加灵活,效率更高,也支持局部刷新。因此应尽量使用RecycleView来展示列表。