Day1:
- 在代码中通过R.string.hello_world可以获得该字符串的引用;
- 在XML中通过@string/hello_world可以获得该字符串的引用;
- string是可以替换的,引用图片换成drawable,布局文件换成layout,云云;
- 声明Activity为主活动需要在Manifest里如下声明:
Day2:
-
四大组件:
- Activity(活动);
- Service(服务);
- Broadcast Receiver(广播接收器);
- Content Provider(内容提供器)。
-
Android Studio的目录结构(Project):
- .gradle .idea:自动生成的文件,无需关心;
- app:开发工作基本在此目录下展开:
- build:编译生成的文件
- libs:第三方jar包存放位置
- androidTest:用来编写Android Test测试用例的,对项目进行一些自动化测试
- main-java:java代码
- main-res:图片、布局、字符串等资源文件
- main-AndroidManifest.xml:整个Android项目的配置文件,注册四大组件,给App添加权限声明
- test:用来编写Unit Test测试用例的,自动化测试的另一种方式
- .gitignore:排除在版本控制之外
- iml文件是所有IntelliJ IDEA项目都会自动生成的一个文件
- build.gradle:指定很多项目构建的相关的配置
- proguard-rules .pro :指定项目代码的混淆规则
- build:编译时,自动生成的文件;
- gradle
- .gitignore
- build.gradle
- grade.properties
- gradlew和gradlew.bat
- iml文件是所有IntelliJ IDEA项目都会自动生成的一个文件
- local.properties:用于指定本机中的Android Sdk路径
- settings.gradle:用于指定项目中所有引入的模块
-
Button和menu以及Toast
- 在布局文件中声明button,以及单独创建Menu的目录和xml文件
- id可以唯一标识这个组件,@+id的方式类似于创建一个id为button_1的按钮,match_parent标识跟随父类的大小,wrap_content表示随内容的大小。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); Button button1 = (Button) findViewById(R.id.button_1); button1.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v){ Toast.makeText(FirstActivity.this, "你点击了按钮1", Toast.LENGTH_LONG).show(); } }); Button backButton = (Button) findViewById(R.id.button_2); backButton.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View vi){ finish(); } }); }
- 在onCreate方法里加载布局文件,通过findViewById该方法找到文件中的组件,设置组件所触发的场景;Toast为提示弹框;finish()表示销毁当前的Activity,与Back键功能一致。
- 注意Menu是一种单独的xml,id同理,title表示菜单显示的字样。
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main,menu); return true; }
- 这一方法表示创建菜单,并加载xml文件中的样式,true表示显示。
@Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()){ case R.id.add_item: Toast.makeText(this,"你点击了Add按钮",Toast.LENGTH_SHORT).show(); break; case R.id.remove_item: Toast.makeText(this,"你点击了Remove按钮",Toast.LENGTH_SHORT).show(); break; default: } return true; }
- 这一方法监听选择菜单栏里的选项时。
Day3:
-
跳转活动(即跳转页面):
-
Intent的概念:
Intent是Android程序中各组件之间进行交互的一种重要方式,可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。一般可被用于启动活动、服务以及发送广播等场景。
-
Intent大致可以分为两种:显式和隐式。
-
显式Intent:(暂时理解为意图非常明显)
Button nextPageButton = (Button) findViewById(R.id.nextPage); nextPageButton.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View vi){ //显式的跳转活动 Intent toNextActivity = new Intent(FirstActivity.this, SecondActivity.class); //启动活动 startActivity(toNextActivity); } });
-
隐式Intent:(并不明确指明想要启动哪一个Activity,而是指定了一系列更为抽象的action和category等信息,交由系统去分析这个Intent,找出合适的Activity去启动)
在Manifest里进行声明Activity的action和categoty信息
只有
和 中的内容同时能够匹配上Intent中指定的action和cartegory时,这个Activity才能响应该Intent。 在Activity中隐式调用:
//隐式调用 Intent back = new Intent("com.xf.activitytest.ACTION_START"); startActivity(back);
可以看到这个时候并没有category的信息,但还是可以调起来的,这是因为xml中声明的category是一种默认的,在调用startActivity()方法的时候,会自动将这个category添加到Intent中。
每个Intent中只能指定一个action,但却能指定多个category
back.addCategory("com.xf.activitytest.MY_CATEGORY");...
-
更多的隐式Intent用法:
隐式Intent,不仅可以启动自己程序内的活动,还可以启动其他程序的活动,使得Android多个应用程序之间的功能共享成为了可能,比如应用程序中需要展示一个网页......
Button openBlibili = (Button) findViewById(R.id.openBrowserBut); openBlibili.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent openBrowser = new Intent(Intent.ACTION_VIEW); openBrowser.setData(Uri.parse("http://www.bilibili.com")); startActivity(openBrowser); } });
实现的效果是通过一个按钮打开bilibili的网页;
Intent.ACTION_VIEW 是Android系统内置的动作,常量值为 android.intent.action.VIEW;
setData()方法接受一个Uri对象,用于指定当前Intent正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse()方法中解析产生的。
还可以在
标签中再配置一个标签,用于更精确指定当前活动能够响应什么类型的数据。比如打开网页的那个activity可以想象它在xml里声明的是 同理,我们用上面这个标签,可以伪造一个响应http数据类型的Activity。
除了http协议外,还可以指定“geo”表示显示地理位置,“tel”表示拨打电话......
Button dialBut = (Button) findViewById(R.id.falseBrowser); dialBut.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent dialIntent = new Intent(Intent.ACTION_DIAL); dialIntent.setData(Uri.parse("tel:10010")); startActivity(dialIntent); } });
Intent.ACTION_DIAL 同样是系统内置动作,data指定了协议是tel。
-
-
-
活动之间的数据传递:
Intent提供了putExtra()方法的重载,可以把我们想要传递的数据暂时存入Intent中。
-
向下一个活动传递数据:
@Override public void onClick(View vi){ //显式的跳转活动 Intent toNextActivity = new Intent(FirstActivity.this, SecondActivity.class); //传递数据 String contentStr = "Hello,MyFriend。We are brother!"; toNextActivity.putExtra("extra_data",contentStr); //打开活动的一种方式 startActivity(toNextActivity); }
存入的方式是key,value,打开的活动通过key去取值
Intent activityMessage = getIntent(); String content = activityMessage.getStringExtra("extra_data"); Log.d(TAG,content); if(!"".equals(content)){ Toast.makeText(this, "收到上一活动的信息:" + content, Toast.LENGTH_SHORT).show(); }
在传入活动的onCreate()方法里通过getIntent获取Intent,并调用getStringExtra()方法;如果传递的是整型数据,则使用getIntExtra()方法;布尔则使用getBooleanExtra()方法......
-
返回数据给上一个活动:
这里我们需要了解3个方法,一个是启动Activity的另一个方法,一个是Activity专门用于向上一个活动返回数据的,最后一个是第一个方法调起活动销毁后的回调方法,可以监听到被销毁的Activity返回的数据。下面依次进行介绍。
-
startActivityForResult()方法
public void startActivityForResult(Intent intent, int requestCode)
该方法期望在打开的活动销毁的时候能够返回一个结果给上一个活动,第二个参数是请求码,用于得到返回信息时确认是哪个活动返回的。(比如在APP首页,点击了轮播广告,广告返回时,根据这个请求码可以判定是哪个广告被点击)
Button nextPageButton = (Button) findViewById(R.id.nextPage); nextPageButton.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View vi){ //显式的跳转活动 Intent toNextActivity = new Intent(FirstActivity.this, SecondActivity.class); //传递数据 String contentStr = "Hello,MyFriend。We are brother!"; toNextActivity.putExtra("extra_data",contentStr); //期望在活动销毁的时候能够返回一个结果给上一个活动 startActivityForResult(toNextActivity, 1); } });
-
setResult()方法
public final void setResult(int resultCode, Intent data)
该方法是专门用于向上一个活动返回数据的,第一个参数用于向上一个活动返回处理结果,一般只用RESULT_OK或RESULT_CANCELED这两个值,第二个参数则把带有数据的Intent返回回去。
销毁活动的方式目前我们知道有2个,一个是finish()一个是back键,这里对活动销毁返回数据做了一个封装,便于复用。
/** * 返回数据给上一活动并销毁当前的活动 * @param key 返回的key * @param value key对应的value * @param resultCode RESULT_OK or RESULT_CANCELED */ public void resultDataAndFinishActivity(String key, String value, int resultCode){ Intent back = new Intent(); back.putExtra(key,value); setResult(resultCode, back); finish(); }
对于Back这种操作,也有相应的监听方法:
//监听返回按键 @Override public void onBackPressed() { //返回数据给上一个Activity data_return resultDataAndFinishActivity("data_return", "Yes,We are 友達", RESULT_OK); }
这里就调用了那个封装的方法。还有一个小插曲,如果这个方法里不执行finish()操作,那么即使按返回键也是返不回去的。
-
onActivityResult()方法
protected void onActivityResult(int requestCode, int resultCode, Intent data)
只要是使用了startActivityForResult()这一方法启动的活动被销毁后,都会走到这个回调里面来。requestCode即第一个方法里的请求码,resultCode为返回数据时的处理结果,data即为数据。
//startActivityForResult()方法调起活动销毁后回调 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode){ case 1 : if(resultCode == RESULT_OK){ String resultDatadata = data.getStringExtra("data_return"); Log.d(TAG, resultDatadata); Toast.makeText(this, resultDatadata, Toast.LENGTH_SHORT).show(); } break; default: } }
-
Day4:
-
Activity的生命周期:
了解的深入有利于写出连贯流畅的App,合理管理应用资源,更好的用户体验......返回栈:
Activity是可以重叠的,点击Back键会销毁最上面的活动,下面的一个活动就会显现出来;
Android是使用Task(任务)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack);
栈是一种后进先出的数据结构,在默认情况下,启动的一个新的活动,它会在栈顶的位置,当这按下Back键或finish()方法销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶位置,系统总会是显示处于栈顶的活动给用户。-
活动状态:
每个Activity在其生命周期中最多可能会有4种状态。- 运行状态:Activity处于栈顶,就处于运行状态,系统最不愿回收的就是处于运行状态的Activity;
- 暂停状态:当Activity不再处于栈顶位置,但仍然可见,就处于暂停状态;(当前活动上有一个对话框,对话框只会暂用屏幕中间的部分区域)处于暂停状态的Activity仍然是完全存活着的,只有在内存极低的情况下才会考虑回收这种活动;
- 停止状态:当Activity不再处于栈顶位置,且完全不可见的时候,就处于停止状态;系统仍然会为其保存相应的状态和成员变量,但这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收;
- 销毁状态:当Activity从返回栈中移除后就变成了销毁状态,系统最倾向回收处于这种状态的活动,从而保证手机的内存充足。
-
活动的生存期:
- onCreate(),在Activity第一次被创建的时候进行调用,在此方法中应完成活动的初始化操作(加载布局、绑定事件);
- onStart(),在Activity由不可见变为可见的时候调用,(我们之前说,不可见的活动为停止状态,也就是从停止状态恢复过来的时候有可能没被系统回收,就执行该方法;否则就是从onCreate()方法过来的);
- onResume(),在Activity准备好和用户进行交互的时候调用,此时的活动一定位于返回栈的栈顶,并且处于运行状态;
- onPause(),在系统准备去启动或者恢复里一个活动的时候调用。通常在此方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但力求效率,不然会影响到新的栈顶Activity的需要;
- onStop(),在Activity完全不可见的时候调用(如果启动的新Activity是一个对话框式的,那么onPause()方法或得到执行,而onStop()并不会得到执行);
- onDestroy(),在Activity被销毁之前调用,之后Activity的状态将变为销毁状态;
- onRestart(),在Activity由停止状态变为运行状态之前调用,Activity被重新启动了;
-
活动还可以分为3种生存期:
- 完整生存期:onCreate()~onDestroy(),一般情况下,一个Activity会在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完成释放内存的操作;
- 可见生存期:onStart()~onStop(),在这个期间内,Activity对于用户而言,总是可见的,通过这两个方法,进行合理地管理对用户可见的资源,比如在onStart()方法中对资源进行加载,在onStop()方法中对资源进行释放,从而保证处于停止状态的Activity不会占用过多的内存;
- 前台生存期:onResume()~onPause(),在这个期间,Activity总是处于运行状态的,平时我们看到和接触最多的也就是这个状态下的活动。
-
Activity被回收了怎么办?
- 处于停止状态的Activity是有可能被系统回收的,如果被回收,Activity存放的数据(非创建时赋值的)就会消失,为了避免这种情况,系统提供了存入和取出的方法;
- 在onSaveInstanceState()方法里存入
//该方法在活动回收之前被调用 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); String tempData = "Hello ,I'm saveData"; outState.putString("miss_key", tempData); }
- 在onCreate()方法里取出
//活动被回收时取得想要存留的值 if(savedInstanceState != null){ String tempData = savedInstanceState.getString("miss_key"); Log.d(TAG, tempData); Toast.makeText(this, tempData, Toast.LENGTH_SHORT).show(); }
-
Activity的启动模式:有四种,standard、singleTop、singleTask、singleInstance,可以在AndroidManifest.xml中通过
标签指定android:lauchMode属性来选择启动模式,standard为默认的模式。 ... - standard:
在不显示指定的情况下,所有Activity都会自动使用这种启动模式;在这种模式下,每当启动一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置,系统这时不会在乎这个Activity是否已经在返回栈中存在,每次启动都会创建该Activity的一个新的实例;
可以通过使用按钮重复启动当前Activity,并打印其类的toString()方法来进行观察;Log.d(TAG, this.toString()); Button testStandardBut = (Button) findViewById(R.id.test_standard); testStandardBut.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MainActivity.class); startActivity(intent); } });
- singleTop:
这种模式下,在启动Activity时如果发现返回栈的栈顶已经是该Activity,则认为可以直接使用它,不会再创建新的活动实例;(这里藏了一句话,如果曾经打开的Activity不再栈顶,处于停止状态或暂停状态,那么还是会创建一个新的实例) - singleTask:
这种模式下,Activity在整个应用程序的上下文中只存在一个实例,每次启动Activity时系统首先会在返回栈中检查是否存在该Activity的实例,如果存在就直接使用该实例,并把在这个Activity之上的所有Activity统统出栈,如果不存在就创建一个; - singleInstance:
不同之前的3种模式,singleInstance模式的Activity会启动一个新的返回栈来管理这个Activity(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈----暂时不能理解这个括号);这样做的意义在于,假设该Activity允许其他程序调用(比如支付宝的付款那一步),我们能通过这一单独的返回栈实现我们的程序和其他程序共享这个Activity的实例,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈。
通过getTaskId()
方法可以得到当前返回栈的id。
- standard:
-
活动的最佳实践:
- 知晓当前是在哪一个活动:
- 新建一个JavaClass,叫做BaseActivity,继承AppCompatActivity,并重写onCreate()方法;
public class BaseActivity extends AppCompatActivity { private static final String TAG = "BaseActivity"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //打印当前的活动的类名 Log.d(TAG, getClass().getSimpleName()); } }
- getSimpleName():返回源代码中给出的底层类的简称。如果底层类是匿名的则返回一个空字符串。
- 随时随地退出程序:
- 将所有的Activity存入集合中,在退出时,遍历这个集合,执行finish()方法即可;在onCreate()方法中存入,在onDestroy()方法中删除;
public class ActivityCollector { public static List
activities = new ArrayList (); public static void addActivity(Activity activity){ activities.add(activity); } public static void removeActivity(Activity activity){ activities.remove(activity); } public static void finishAll(){ for (Activity activity : activities ) { if (!activity.isFinishing()) activity.finish(); } activities.clear(); //杀掉当前进程 android.os.Process.killProcess(android.os.Process.myPid()); } } public class BaseActivity extends AppCompatActivity { private static final String TAG = "BaseActivity"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //打印当前的活动的类名 Log.d(TAG, getClass().getSimpleName()); //添加创建的活动 ActivityCollector.addActivity(this); } @Override protected void onDestroy() { super.onDestroy(); ActivityCollector.removeActivity(this); } }
- 知晓当前是在哪一个活动:
-
改造Toast,使其不会多次重复相同提示:
public class ToastUtil { private static Toast toast; public static void showToast(Context context, String content, int time){ if (toast == null) { toast = Toast.makeText(context, content, time); }else{ toast.setText(content); } toast.show(); } }
- 常用控件的使用方法:
- TextView:
- 布局文件中:
android:gravity表示文字的对齐方式,可选值有top、bottom、left、right、center等,可以用“|”来同时指定多个值,center表示垂直和水平方向都居中对齐;
- 布局文件中:
- Button:
- 布局文件中:
textAllCaps设置关于英文字母是否都转为大写;在代码中,可以通过匿名内部类或者实现接口的方式,实现对按钮点击的监听;
- 布局文件中:
- EditText:
- 布局文件中:
hint表示提示信息,lines表示最大行数为2行,超出2行后,文本会向上滚动,空间高度不会被拉伸; - 代码中:
可以通过editText.getText().toString()
方法获得文本框中的内容;
- 布局文件中:
- ImageView:
- 布局文件中:
android:src指定图片的路径; - 代码中:
可以通过setImageResource()
方法更改Viewx所显示的图片;
- 布局文件中:
- ProgressBar:
- 布局文件中:
- Android控件的可见性:
所有的Android控件都具有这个属性,可以通过android:visibility
进行指定,可选值有三种:visible、invisible和gone;分别是可见的、透明的(不可见但还占着地方)、不可见(不占地方);同时可以根据setVisibility()
方法,传入View.VISIBLE、View.INVISBLE和View.GONE进行控制; - 换成进度条样式:
指定了进度条长度为100,在代码中可以通过style="?android:attr/progressBarStyleHorizontal" android:max="100"
getProgress()
和setProgress()
进行读取值;
- 布局文件中:
- AlertDialog:
- 说明:可以在当前的界面弹出一个对话框,其置顶于所有界面元素之上,能够屏蔽掉其他控件的交互能力,一般用于提示一些非常重要的内容或者警告信息。
...在按钮的监听方法里 AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this); dialog.setTitle("这是一个提示框"); dialog.setMessage("确定要退出程序吗?"); dialog.setCancelable(true); dialog.setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "ByeBye", Toast.LENGTH_SHORT).show(); } }); dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "取消", Toast.LENGTH_SHORT).show(); } }); dialog.show();
setPositiveButton()
为对话框设置确定按钮的点击事件,setNegativeButton()
设置取消按钮的点击事件; - ProgressDialog:
- 说明:控件名称很直白的告诉我们,这是一个带有进度条的对话框,用法和AlertDialog类似;
ProgressDialog dialog = new ProgressDialog(MainActivity.this); dialog.setTitle("这是一个加载提示框"); dialog.setMessage("Loading......"); dialog.setCancelable(true); dialog.show();
- 如果在setCancelable()中传入了false,表示ProgressDialog是不能通过Back键取消的,这时必须根据数据加载的情况调用
dismiss()
方法进行关闭,否则,会一直存在。
- TextView:
Day5:
- 详解4种基本布局:
布局是一种可用于放置很多控件的容器,除了放置控件,也可以放置布局,通过多层布局的嵌套,从而完成一些比较复杂的界面。
- 线性布局:
- LinearLayout布局会将它所包含的控件在线性方向上依次排列,通过
android:orientation
属性可以指定是horizontal(水平方向-默认值)、vertical(垂直方向)排列; - 需要注意的是,如果是horizontal,控件的宽度就不能指定为match_parent;如果是vertical,控件的高度就不能指定为match_parent;原因是会充满整个布局;
-
android:layout_gravity
用于指定控件在布局中的对齐方式,同样也有2个方向上的注意:horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,长度就会发生改变,因而无法指定对齐方式;同理vertical时,只有水平方向上的对齐方式才会生效; -
android:layout_weight
,这个属性允许我们使用比例的方式来指定控件的大小,它在手机屏幕的适配性方面祈祷非常重要的作用,下面演示xml;
- 在使用
android:layout_weight
属性后,控件的宽度不应该再由width来决定,写成0dp是一种比较规范的写法,dp是android中用于指定控件的大小、间距等属性的单位; - 系统如何确定1所代表的的值呢?它会把当前布局文件下的所有控件指定的weight值相加,上面的所占大小就各式0.5了,也就平分了;
- 我们还可以指定部分控件的weight值,将上面的button中,宽度指定为wrap_content,去掉weight,效果会更好;EditText是仅有的1,会占满屏幕所有的剩余控件;有利于适配性;
- LinearLayout布局会将它所包含的控件在线性方向上依次排列,通过
- 相对布局:
- RelativeLayout显得更加随意一些,它可以通过相对定位的方式让控件出现在 布局的任何位置,也正因为如此,RelativeLayout中的属性非常多,不过观察以下xml信息就可得知,这些属性都是有规律可循的:
- 帧布局:
- FrameLayout相比LinearLayout、RelativeLayout就很简单了,应用场景也少了很多。这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上角;
- 总体来讲,FrameLayout由于定位方式的缺失,导致它的应用场景也比较少,在介绍碎片的时候,会涉及到它;
- FrameLayout相比LinearLayout、RelativeLayout就很简单了,应用场景也少了很多。这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上角;
- 百分比布局:
- LinearLayout、RelativeLayout、FrameLayout3种布局是从Android1.0的版本中就开始支持了,一直沿用到现在;不过只有LinearLayout支持使用layout_weight属性来实现按比例指定控件大小的功能;使用RelativeLayout来实现两个按钮平分布局宽度的效果,是比较困难的;为此Android引入了一种全新的布局——百分比布局;这种布局允许直接指定控件在布局中所占的百分比,可以轻松的实现平分布局甚至任意比例分割布局的效果;
- 官方为RelativeLayout和FrameLayout进行了功能拓展,提供了PercentRelativeLayout和PercentFrameLayout这两个全新的布局;
- 添加百分比布局的依赖,打开app/buid.gradle文件,在dependencies闭包中添加如下内容:
compile 'com.android.support:percent:24.2.1'
- 每当修改了任何gradle文件时,Android Studio都会弹出一行提示,末尾单词Sync Now的意思是“现在同步”,点击即可进行同步;
- 下面进行演示PercentFrameLayout布局:
- 由于PercentLayout并不是内置在系统SDK当中的,所以需要把完整的包路径写出来,然后还必须定义一个app的命名空间,这样才能使用百分比布局的自定义属性;
- 布局文件中,使用了app、android的前缀,就是得益于在开头部分声明了;因为是拓展,PercentFrameLayout会继承FrameLayout的特性,所有的控件默认都是摆放在布局的左上角;
- PercentRelativeLayout的用法与上面类似,下面做简单介绍:
- ps:Android中还有AbsoluteLayout、TableLayout等布局,用的相对较少;
- 线性布局:
- 自定义控件:
上面学习了一些Android中的常用控件以及基本布局的用法,现在介绍一下这些控件和布局的继承结构:
可以看到,控件直接或间接继承View,布局直接或间接继承ViewGroup。View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,我们使用各种控件其实就是在View的基础之上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器。
当自带的控件不能满足需求时,我们可以利用上面的继承结构来创建自定义控件。- 引入布局
- 遇到重复的界面我们是否需要进行重复的编码操作呢?引入布局的概念由此而生;
- 新建一个布局title.xml
-
android:background
用于为布局或控件指定一个背景,可以使用颜色或图片来进行填充;android:layout_margin
用于指定控件在上下左右方向上偏移的距离,也可以使用android:layout_marginLeft
或android:layout_marginTop
等属性来单独指定控件在某个方向上偏移的距离; - 使用时在布局文件中引入即可:
- 在Activity中将系统自带的标题栏隐藏掉:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) actionBar.hide(); } }
- 创建自定义控件
- 上面的引入布局中我们想让back起作用,也只能不断的在每个Activity中findViewById()吗?这种情况可以通过使用自定义控件的方式来解决。
public class TitleLayout extends LinearLayout { public TitleLayout(Context context, AttributeSet attrs){ super(context, attrs); LayoutInflater.from(context).inflate(R.layout.title, this); } }
- 新建一个JavaClass,继承LinearLayout(为什么是Linear?)
- 在构造方法里(在布局中引入该类就会被调用)调用了父类的构造函数,借助LayoutInflater实现对标题栏布局进行动态加载;
LayoutInflater.from(context).inflate(R.layout.title, this);
中,this代表给加载好的布局再添加一个父布局; - 在xml中进行引入:
- 在代码中绑定事件:
public class TitleLayout extends LinearLayout { public TitleLayout(Context context, AttributeSet attrs){ super(context, attrs); LayoutInflater.from(context).inflate(R.layout.title, this); backOfTitle(); editOfTitle(); } public void backOfTitle(){ Button backBut = (Button) findViewById(R.id.title_back); backBut.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ((Activity) getContext()).finish(); } }); } public void editOfTitle(){ Button editBut = (Button) findViewById(R.id.title_edit); editBut.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getContext(), "你点击了Edit按钮。", Toast.LENGTH_SHORT).show(); } }); } }
- 这里的监听不是以
new View
的形式,可能是因为布局类的原因;
- 引入布局
- 最常用和最难用的控件ListView:
- ListView的简单用法:
- xml:
- 代码中:
数据无法直接被放入listview布局中,需要用适配器进行加载,ArrayAdapter就是一种适配器;泛型的使用有利于加载更加复杂的数据,构造方法中传入上下文,ListView子项布局的id(意味着我们可以传入自己定义的布局样式),以及要适配的数据;private String[] data = { "Apple","Banana","Orange","Watermelon", "Pear","Grape","Pineapple","Strawberry","Cherry","Mango", "Apple","Banana","Orange","Watermelon", "Pear","Grape","Pineapple","Strawberry","Cherry","Mango"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ArrayAdapter
adapter = new ArrayAdapter ( MainActivity.this, android.R.layout.simple_list_item_1, data ); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, fruitList.get(position).getName(), Toast.LENGTH_SHORT).show(); } }); } android:R.layout.simple_list_item_1
是Android内置的布局文件,里面只有一个TextView,可用于简单显示一段文本;最后调用ListView.setAdapter()
,将构建好的适配器对象传递进去,完成控件与数据之间的关联。下面是一个很好懂的监听点击的方法,如果要取出数组data,则把角标放上即可吧。
- xml:
- 定制ListView的界面:
- 新建布局样式文件:
- 定义一个实体类,作为适配器的适配类型
public class Fruit { private String name; private int imageId; public Fruit(String name, int imageId){ this.name = name; this.imageId = imageId; } public String getName(){ return name; } public int getImageId(){ return imageId; } }
- 创建一个自定义的适配器,继承ArrayAdapter
自定义的Adapter在构造方法中将传入的布局子项id赋给了属性,这个时候我们清楚那个传入的id就应该是我们自定义的那个布局;public class FruitAdapter extends ArrayAdapter
{ private int resourceId; public FruitAdapter(Context context, int textViewResourceId, List objects){ super(context, textViewResourceId, objects); resourceId = textViewResourceId; } @NonNull @Override public View getView(int position, View convertView, ViewGroup parent) { Fruit fruit = getItem(position); View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image); TextView fruitName = (TextView) view.findViewById(R.id.fruit_name); fruitImage.setImageResource(fruit.getImageId()); fruitName.setText(fruit.getName()); return view; } }
重写的getView()方法,在每个子项被滚动到屏幕内的时候会被调用;
分析下getView()里面我们应该干什么,第一步将自定义的布局加载出来,第二步将给控件上放入数据(图片、文本等);
那么数据从哪里来?
在实例化Adapter的时候会传入Data,getView()提供了position,通过getItem(position)就可得到传入的类型(当初泛型指定的类型)
还有一点就是inflate()方法,这里第三个参数是false,表示只让在父布局中声明的layout属性生效,但不会为这个View添加父布局,因为一旦View有了父布局之后,它就不能添加到ListView中了,是ListView的标准写法。 - 最后在Activity中调用:
只要修改自定义布局的样式,就可以定制复杂的界面了public class MainActivity extends AppCompatActivity { private List
fruitList = new ArrayList (); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initFruits(); FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, fruitList.get(position).getName(), Toast.LENGTH_SHORT).show(); } }); } private void initFruits(){ for (int i = 0; i < 2; i++) { Fruit apple = new Fruit("Apple", R.drawable.apple_pic); fruitList.add(apple); Fruit banana = new Fruit("Banana", R.drawable.banana_pic); fruitList.add(banana); Fruit orange = new Fruit("orange", R.drawable.orange_pic); fruitList.add(orange); Fruit watermelon = new Fruit("watermelon", R.drawable.watermelon_pic); fruitList.add(watermelon); Fruit pear = new Fruit("pear", R.drawable.pear_pic); fruitList.add(pear); Fruit grape = new Fruit("grape", R.drawable.grape_pic); fruitList.add(grape); Fruit pineapple = new Fruit("pineapple", R.drawable.pineapple_pic); fruitList.add(pineapple); Fruit strawberry = new Fruit("strawberry", R.drawable.strawberry_pic); fruitList.add(strawberry); Fruit cherry = new Fruit("cherry", R.drawable.cherry_pic); fruitList.add(cherry); Fruit mango = new Fruit("mango", R.drawable.mango_pic); fruitList.add(mango); } } }
- 新建布局样式文件:
- 提升效率:
目前我们ListView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局加载了一遍,当ListView快速滚动的时候,会成为性能的瓶颈;- convertView用于将之前加载好的布局进行缓存,以便之后可以进行重用;控件可以采用定义内部类的形式进行保存;(暂理解为布局都被缓存了,那么布局上的控件也应该是对应的控件,setTag()起到了关键作用)
Adapter: @NonNull @Override public View getView(int position, View convertView, ViewGroup parent) { Fruit fruit = getItem(position); View view; ViewHolder viewHolder; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); viewHolder = new ViewHolder(); viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image); viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name); view.setTag(viewHolder); }else{ view = convertView; viewHolder = (ViewHolder) view.getTag(); } viewHolder.fruitImage.setImageResource(fruit.getImageId()); viewHolder.fruitName.setText(fruit.getName()); return view; } class ViewHolder{ ImageView fruitImage; TextView fruitName; }
- convertView用于将之前加载好的布局进行缓存,以便之后可以进行重用;控件可以采用定义内部类的形式进行保存;(暂理解为布局都被缓存了,那么布局上的控件也应该是对应的控件,setTag()起到了关键作用)
- ListView的简单用法: