Claws Garden

Android基础02——安卓基础编程

Activity

创建

  1. 创建XXActivity.java,继承AppCompatActivity
  2. 在AndroidManifest.xml中注册,写法形如<activity android:name="chapter.android.aweme.ss.com.chapter2.xmlparser.XmlActivity" />
  3. 编写布局xml,放在res/layout目录下
  4. 在XXActivity.java类中重写onCreate方法,并用setContentView(xml)展示相应的布局,形如setContentView(R.layout.activity_main);

跳转与关闭

每个Activity相当于一个页面,应用的页面之间可以进行跳转。使用startActivity()方法进行跳转。在跳转时,必须传给该方法一个Intent(意图)。意图至少要指明当前上下文和跳转的页面Activity类,也可以增加其他额外的数据。最简单的意图形如new Intent(this, XmlActivity.class),传给startActivity()后可以跳转到XmlActivity。

intent包含四个重要的内容:

在定义intent的时候可以对这四个信息进行set,而跳转的目标界面也可以拿到随之传来的这四个内容,提供一定的服务。

此外,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。在真正使用时,返回键退出当前页面的时候也会调用这个方法。可以对该方法进行重写。

生命周期

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

需要注意的是,不仅仅按钮能够通过点击和用户交互,文本框、图片等很多组件也可以设置点击的监听器,对用户点击的动作做出反应。

ViewGroup

下图表示了诸多View和ViewGroup的继承关系。

View

一个布局文件的最外层应当是一个ViewGroup。

屏幕适配

wrap_content、match_parent、weight、gravity这些关键字的属性可以将组件的大小、位置自动适应内容或者外层布局。

minWidth、minHeight 可以约束一个组件的最小尺寸。

Nine-Patch点九图可以依据内容自动拉伸,而且不影响视觉效果。

事件传递

触摸事件对应MotionEvent类,三种事件类型:

这三种事件发生时,第一个接收到事件的会是当前的Activity,然后是页面布局上最外层的ViewGroup,接着是下层的ViewGroup或者View,层层嵌套。每一层都可以自己决定当有事件传来时如何处理这个事件。

处理事件有三个动作:

首先要决定如何分发这个事件,然后分发给下层的过程中可以拦截,如果不分发给下层则会自己消费掉。下图即为正常情况下的事件分发逻辑:

event

消费即意味着事件传递的终点。super表示类似return super.dispatchTouchEvent(ev);这样的情况。ViewGroup的super默认都是返回false的。

也可以参考下面这张图:

TouchEvent

高级UI组件之列表

ListView

是一个显示可滚动项目的视图组件。需要配合使用Adapter将内容插入列表中。Adapter负责将每一个列表元素的视图样式和数据结合起来,填入ListView中去。

ListView

ListView每次会去找Adapter请求需要的View,Adapter内部将数据和item的布局结合后返回给ListView这个Item的View,这样ListView就可以显示每一个Item了。

ListView本身具有缓存复用的机制,可以防止一次加载过多的item导致卡顿。每次ListView只会加载稍多于一个屏幕能够显示的item数量,滑动屏幕时会将移出屏幕的item回收并加入新出现的item,大大提高了加载效率。不过这些都是ListView内部实现,我们使用者不用太在意。

重点是Adapter。有四种Adapter可供我们使用:

其中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}

总结起来,至少要自己实现三个方法和一个内部类:

如果需要监听点击事件,还需要一个接口和在Holder中帮助View传递给监听者(Activity)。

另外,RecycleView还可以借助ItemAnimator实现一些小动画,更加好看。

在内部的实现中,RecycleView采用的是三级缓存,比ListView更加灵活,效率更高,也支持局部刷新。因此应尽量使用RecycleView来展示列表。

本节示例工程

https://github.com/jingjiecb/Chapter-2

#Android