第一个android项目:导航栏目。
1、初探Activity
导语:
Activity作为Android四大控件之一,应用程序的每一个界面都是一个Activity,所以也有人称其为视图界面。从字面的意思去理解,Activity具有活动的意思,我们在应用中进行的操作都是集中在Activity上面完成,例如拨号、拍照、发送email、看地图。每一个activity被给设置到一个窗口,在上面可以绘制交互界面。一个应用程序通常由多个activities组成,他们通常是松耦合关系,通常一个应用程序包含有一个主Activity,即点击桌面图标的时候首先进入的Activity。
简单的一个Activity实例:
首先建立一个空的Activity项目,android studio会自动给创建activity_main.xml、MainActivity.java、AndroidManifest.xml三个文件。 AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myfragmentdemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:label="Fruits">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Activity必须在AndroidManifest.xml这个文件里面进行注册,< application /> :应用的声明。 这个元素包含了子元素,这些子元素声明了应用的组件,元素的属性将会影响应用下的所有组件。很多属性为组件设置了默认值,有些属性设置了全局值并且不能被组件修改。activity元素的name属性指定了实现了这个activity的 Activity的子类。icon和label属性指向了包含展示给用户的此activity的图标和标签的资源文件。
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
表示该Activity是主活动,从android系统中点击应用图标直接进入该活动。
MainActivity.java:
解析MainActivity这个文件之前,我们需要先了解下Activity对应的生命周期:
- Active/Running: Activity处于活动状态,此时Activity处于栈顶,是可见状态,可与用户进行交互。
- Paused:当Activity失去焦点时,或被一个新的非全屏的Activity,或被一个透明的Activity放置在栈顶时,Activity就转化为Paused状态。但我们需要明白,此时Activity只是失去了与用户交互的能力,其所有的状态信息及其成员变量都还存在,只有在系统内存紧张的情况下,才有可能被系统回收掉。
- Stopped: 当一个Activity被另一个Activity完全覆盖时,被覆盖的Activity就会进入Stopped状态,此时它不再可见,但是跟Paused状态一样保持着其所有状态信息及其成员变量。
- Killed:当Activity被系统回收掉时,Activity就处于Killed状态。 Activity会在以上四种形态中相互切换,至于如何切换,这因用户的操作不同而异。了解了Activity的4种形态后,我们就来聊聊Activity的生命周期。
所谓的典型的生命周期就是在有用户参与的情况下,Activity经历从创建,运行,停止,销毁等正常的生命周期过程。我们这里先来介绍一下几个主要方法的调用时机,然后再通过代码层面来验证其调用流程。
- onCreate : 该方法是在Activity被创建时回调,它是生命周期第一个调用的方法,我们在创建Activity时一般都需要重写该方法,然后在该方法中做一些初始化的操作,如通过setContentView设置界面布局的资源,初始化所需要的组件信息等。
- onStart : 此方法被回调时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,因此无法与用户进行交互。可以简单理解为Activity已显示而我们无法看见摆了。
- onResume : 当此方法回调时,则说明Activity已在前台可见,可与用户交互了(处于前面所说的Active/Running形态),onResume方法与onStart的相同点是两者都表示Activity可见,只不过onStart回调时Activity还是后台无法与用户交互,而onResume则已显示在前台,可与用户交互。当然从流程图,我们也可以看出当Activity停止后(onPause方法和onStop方法被调用),重新回到前台时也会调用onResume方法,因此我们也可以在onResume方法中初始化一些资源,比如重新初始化在onPause或者on4 Stop方法中释放的资源。
- onPause : 此方法被回调时则表示Activity正在停止(Paused形态),一般情况下onStop方法会紧接着被回调。但通过流程图我们还可以看到一种情况是onPause方法执行后直接执行了onResume方法,这属于比较极端的现象了,这可能是用户操作使当前Activity退居后台后又迅速地再回到到当前的Activity,此时onResume方法就会被回调。当然,在onPause方法中我们可以做一些数据存储或者动画停止或者资源回收的操作,但是不能太耗时,因为这可能会影响到新的Activity的显示——onPause方法执行完成后,新Activity的onResume方法才会被执行。
- onStop : 一般在onPause方法执行完成直接执行,表示Activity即将停止或者完全被覆盖(Stopped形态),此时Activity不可见,仅在后台运行。同样地,在onStop方法可以做一些资源释放的操作(不能太耗时)。
- onRestart :表示Activity正在重新启动,当Activity由不可见变为可见状态时,该方法被回调。这种情况一般是用户打开了一个新的Activity时,当前的Activity就会被暂停(onPause和onStop被执行了),接着又回到当前Activity页面时,onRestart方法就会被回调。
- onDestroy :此时Activity正在被销毁,也是生命周期最后一个执行的方法,一般我们可以在此方法中做一些回收工作和最终的资源释放。 介绍完Activity的生命周期,我们回到MainActivity.java这个文件,一般MainActivity类继承于AppCompatActivity类,在该类中,需要重写onCreate方法,在这个方法里,必须调用setContentView(),以定义 Activity 用户界面的布局,另外你还可以在这个方法里初始化一些数据。
activity_main.xml:
这个XML文件与我们上面讲的MainActivity.java有紧密的关系,上面我们讲到onCreate中必须调用setContentView()方法来加载布局文件,这里MainActivity中加载的布局文件就是activity_main。语法如下:
setContentView(R.layout.activity_main);
这便使得两个文件关联起来,在activity_main中我們可以定义当前主活动中所需要布局信息。 例子:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</RelativeLayout>
上述代码定义了一个主页,采用RelativeLayout相对布局,主页中有一个RecyclerView滚动控件。一下简单的介绍下几个常用的布局和控件:
四大布局方式:线性布局(LinearLayout)、相对布局(RelativeLayout)、帧布局(FrameLayout)、表格布局(TableLayout)。
- LinearLayout (线性布局): 说到LinearLayout, 我想说一下流式布局。其实LinearLayout就是流式布局,流式布局有个特点,就是下一个控件的坐标原点由上一个控件来决定,你可以沿水平方向或者垂直方向上来排列你的控件。
- RelativeLayout (相对布局):上面也说了一下相对布局, 相对布局的本质就是以不变应万变。也就是说相对布局可以根据已经固定的控件来确定其他新加控件的位置。
- 帧布局 (FrameLayout):说到帧布局, 就比较简单了,而且比较好理解,并且帧布局的用处不是很多,但他的存在还是有他的必要性的。FrameLayout中的Frame和iOS中的Frame不是一个概念,在iOS中的Frame你可以指定任意的坐标,而这个坐标点时相对于父视图的。FrameLayout中的Frame的坐标原点是屏幕的左上角,位置固定,你只需为控件指定大小即可。 4.表格布局(TableLayout):如果你接触过Web前端的东西的话,虽然常用的时div + css , 但是Web前端也是有表格布局的。在安卓开发中的表格布局和Web前端中的表格布局的概念类似,也就是通过画表表格的方式来实现布局。 在表格布局中,整个页面就相当于一张大的表格,控件就放在每个Cell中。
常用的控件:
- TextView:TextView就是文本视图,只是用来显示文字的。
- Button:在Android中的按钮就叫Button。
- EditText:Activity添加一个输入框,在Android中输入框的类型和标签都是EditText。
- AlterDialog(警告框):Toast用来显示提示内容,而AlterDialog是警告框,上面可以有一些控件,比如按钮等。
- ProgressBar(进度条):进度条,就是平时下载东西常见到表示下载进度的控件。
- ProgressDialog(进度提示框):ProgressDialog说白了就是在AlterDialog上添加Progress, ProgressDialog不需要在xml中进行配置,直接在代码中进行生成即可。下方是在按钮点击的委托代理方法中添加的ProgressDialog,点击按钮时就显示ProgressDialog。 下图给出View的继承关系,能够更好的理解布局、控件之间的关系:

2、初探RecyclerView控件,最常用的控件之一。
从Android 5.0开始,谷歌公司推出了一个用于大量数据展示的新控件RecylerView,可以用来代替传统的ListView,更加强大和灵活。RecyclerView的官方定义如下:
A flexible view for providing a limited window into a large data set.
从定义可以看出,flexible(可扩展性)是RecyclerView的特点。
RecyclerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字Recyclerview即回收view也可以看出。 RecylerView相对于ListView的优点罗列如下:
- RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。直接省去了listview中convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
- 提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。
- 设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式 例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还StaggeredGridLayoutManager等)。也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现GridView的效果等多种效果。
- 可设置Item的间隔样式(可绘制) 通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去书写代码。 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。 下面通过例子来说明Recycler的使用: 我们在activity_main中指定了Recyclerview作为我们要展示主要内容的控件,通过该控件,动态的加载我们需要的每一个item对象:
package com.example.homepage;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class HomeFragmentAdapter extends RecyclerView.Adapter {
private static final int BANNER = 0;
private static final int CHANNEL= 1;
private static final int ACT = 2;
private int currentType = BANNER;
private Context context;
private ResultBean result;
private LayoutInflater layoutInflater;
public HomeFragmentAdapter(Context context, ResultBean result) {
super();
this.context = context;
this.result = result;
layoutInflater = LayoutInflater.from(context);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if(viewType == BANNER) {
return new BannerViewHolder(context,layoutInflater.inflate(R.layout.home_banner,null));
}else if (viewType == CHANNEL) {
return new ChannelViewHolder(context,layoutInflater.inflate(R.layout.home_gv_channel,null));
}else if (viewType == ACT) {
return new ActViewHolder(context,layoutInflater.inflate(R.layout.home_act,null));
}
return null;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if(getItemViewType(position) == BANNER) {
BannerViewHolder bannerViewHolder = (BannerViewHolder)holder;
bannerViewHolder.setData(result.getBannerInfo());
}else if (getItemViewType(position) == CHANNEL) {
ChannelViewHolder channerViewHolder = (ChannelViewHolder)holder;
channerViewHolder.setData(result.getChannelInfo());
}else if (getItemViewType(position) == ACT) {
ActViewHolder actViewHolder = (ActViewHolder)holder;
actViewHolder.setData(result.getActinfo());
}
}
@Override
public int getItemViewType(int position) {
switch (position){
case BANNER:
currentType = BANNER;
break;
case CHANNEL:
currentType = CHANNEL;
break;
case ACT:
currentType = ACT;
break;
}
return currentType;
}
@Override
public long getItemId(int position) {
return super.getItemId(position);
}
@Override
public int getItemCount() {
return 3;
}
}
上述代码:onCreateViewHolder方法继承自RecyclerView.Adapter,作用是加载布局,通过layoutflater的inflate方法动态的加载布局,onBindViewHolder方法也是继承自RecyclerView.Adapter,该方法是将数据动态绑定到布局上面。这里采用ViewHolder来动态绑定数据;下面以ChannelView为例讲述ViewHolder的使用:
ViewHolder是什么:
ViewHolder通常出现在适配器里,为的是listview滚动的时候快速设置值,而不必每次都重新创建很多对象,从而提升性能。在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。ListView加载数据都是在public View getView(int position, View convertView, ViewGroup parent) {}方法中进行的(要自定义listview都需要重写listadapter:如BaseAdapter,SimpleAdapter,CursorAdapter的等的getvView方法),优化listview的加载速度就要让convertView匹配列表类型,并最大程度上的重新使用convertView。 RecyclerView将ViewHolder和Adapter都作为内部类,写在了RecyclerView中。先不管这把所有类都写在RecyclerView内部的做法是否好,但是ViewHolder作为RecyclerView内部复用的单位,直接避免了不必要的findViewById,而在ListView中则需要我们自己定义ViewHolder。
现在我们自定义一个ViewHolder类,用来满足不同的Item获取数据。下面看其中ChannelViewHolder的实现:
package com.example.homepage;
import android.content.Context;
import android.view.View;
import android.widget.GridView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class ChannelViewHolder extends RecyclerView.ViewHolder {
private Context mContext;
private GridView home_gv_channel;
public ChannelViewHolder(Context context,@NonNull View itemView) {
super(itemView);
this.mContext = context;
home_gv_channel = (GridView)itemView.findViewById(R.id.home_gv_channel);
}
public void setData(List<ChannelInfoBean> channelInfo) {
ChannelAdapter adapter = new ChannelAdapter(mContext, channelInfo);
home_gv_channel.setAdapter(adapter);
}
}
该方法继承了RecyclerView.ViewHolder,调用构造函数,满足了一个布局只调用一次控件加载。另外其中写了新的方法setData用来控制数据的动态加载;数据的加载我们可以嵌套调用Adapter来进一步布局Channel内部布局:
package com.example.homepage;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import java.util.List;
public class ChannelAdapter extends BaseAdapter {
private Context mContext;
private List<ChannelInfoBean> channelInfos;
public ChannelAdapter(Context context, List<ChannelInfoBean> channelInfos) {
this.mContext = context;
this.channelInfos = channelInfos;
}
@Override
public int getCount() {
return channelInfos.size();
}
@Override
public Object getItem(int position) {
return channelInfos.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = View.inflate(mContext, R.layout.item_channel,null);
viewHolder.iv_channel = (ImageView)convertView.findViewById(R.id.iv_channel);
viewHolder.tv_channel = (TextView) convertView.findViewById(R.id.tv_channel);
convertView.setTag(viewHolder);
}else {
viewHolder = (ViewHolder) convertView.getTag();
}
ChannelInfoBean data = channelInfos.get(position);
String imageUrl = data.getImage();
String text = data.getImageId();
Glide.with(mContext).load(imageUrl).into(viewHolder.iv_channel);
viewHolder.tv_channel.setText(text);
return convertView;
}
private static class ViewHolder {
ImageView iv_channel;
TextView tv_channel;
}
}
现在我们来看一般的非RecyclerAdapter的Adapter的使用,ChannelAdapter继承自BaseAdapter,他没有使用ViewHolder来加载布局,而是在getView里面直接获取View值。每一个Adapter我们需要重写getView(),getCount(),gerItem()方法。注意看getView方法,其中对convertView的使用做了判断,如果使用RecyclerView中ViewHolder,我们将避免判断convertView判断和convertView.setTag(viewHolder)等,getView中使用ViewHolder,设置tag来防止多次调用加载View,提高运行效率。