Android第一行代码读书笔记 - 第三章

====================================

====== 第三章:UI开发的点点滴滴 ======

====================================

界面设计和功能实现同样重要。界面美观可以增加用户粘性。

1、如何编写程序界面。

我们不建议使用可视化见面来进行拖拽编写布局,因为可视化编辑不利于你去真正了解界面背后的实现原理。通过可视化界面写出的界面通常不具备很好的屏幕适配性。并且编写复杂界面时无法胜任。因此本书的所有界面都是通过编写xml代码来实现的。

2、重用控件的使用方法:

新建一个UIWidgetTest项目。

2.1 TextView:听说是最简单的控件,显示一段文本信息。修改activity_main.xml中代码如下:

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android

android:orientation=“vertical”

android:layout_width=“match_parent”

android:layout_height=“match_parent”>

<TextView

android:id=“@+id/text_view”

android:layout_width=“match_parent”

android:layout_height=“wrap_context”

android:text=“This is TextView” />

</LinearLayout>

match_parent、fill_parent、wrap_content都是控件的可选属性。其中,match_parant和fill_parent的意义相同,官方更加推荐match_parent。wrap_context表示让当前控件的大小刚好可以包住里面的内容,也就是控件内容决定当前控件的大小。当然你也可以指定控件一个特定值的宽高的大小。(但是这样可能会出现屏幕的适配问题)

如需修改textview里面的文字属性,增加android:textSize=“24sp” android:textColor=“#00ff00”

Android中字体大小使用sp作为单位。

3.2.2 Button

添加一个Button,

<Button

android:id=“@+id/button”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

andoird:text=“Button” />

我们在布局文件里面设置的文字是Button,但是最终显示结果却是BUTTON,这是由于系统会对button中的所有英文字母自动进行大写转换,如果这不是想要的效果,可以使用如下配置来禁用这一默认特新:Android:textAllCaps=“false”

接着,在MainActivity中添加按钮的点击逻辑

Public class MainActivity extends AppCpmpatActivity {

@ovrride

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Button button = (Button)findViewById(R.id.button);

button.setOnClickListener(new View.onClickListener() {

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.button: {

// 点击按钮的逻辑

}break;

default:

break;

}

}

});

}

}

如果不喜欢使用匿名类的方式来注册监听器,也可以使用实现接口的方式来进行注册

Public class MainActivity extends AppCompatActivity implements View.OnClickListener {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Button button = (Button)findViewById(R.id.button);

button.setOnClickListener(this);

}

@Override

public void onClick(View v) {

switch(v.getId()) {

case R.id.button : {

// 点击逻辑

}break;

default:

break;

}

}

3.2.3 EditText(输入框)

一样的,android:hint=“placeholder text”, android:maxLines=“2”

3.2.4 ImageView

显示图片的控件,图片通常都是放在drawable开头的目录下

创建一个drawable-xhdpi的文件夹,放入两张图片

<ImageView

android:id=“@+id/image_view”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:src=“@drawable/img_1” />

3.2.5 ProcessBar(中间圆圈进度旋转)

<ProcessBar

android:id=“@+id/progress_bar”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

style=“?android:attr/progressBarStyleHorizontal”

android:max=“100” />

所有控件都可以设置一个属性visibility,可选值为visible可见的(默认值)、invisible不可见和gone不仅不可见而且不占任何屏幕控件。我们还可以通过代码来设置控件的可见性,使用的是视图的setVisibility(),可以传入View.VISIBLE、View.INVISIBLE、View.GONE

if (progressBar.getVisibility() == View.GONE) {

progressBar.setVisibility(View.VISIBLE);

} else {

progressBar.setVisibility(View.GONE);

}

// 当修改了progressBar的样式之后,修改如下代码,就可以点击button的时候让progress一点点增加

int progress = progressBar.getProgress();

progress += 10;

progressBar.setProgress(progress);

3.2.6 AlertDialog

AlertDialog可以在当前界面弹出一个对话框,并且是置顶于所有控件之上,能够屏蔽掉其他控件的交互能力,因此AlertDialog一般都是用于提示一些非常重要的内容或者警告信息。

我们现在可以在MainActivity中写上如下代码。

public void onClick(View v) {

switch (v.getId()) {

case R.id.button: {

AlertDialog.Bulder dialog = new AlertDialog.Bulder(MainActivity.this);

dialog.setTitle(“This is Dialog”);

dialog.setMessage(“Something important.”);

dialog.setCancelable(false);

dialog.setPositiveButton(“OK”, new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

}

});

dialog.setNegativeButton(“Cancel”, new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

}

});

dialog.show();

break;

default:

break;

}

}

}

setPositiveButton()方法为对话框设置确定按钮点击事件,setNegativeButton()方法设置取消按钮点击事件。最后调用show()方法显示出来,

3.2.7 ProgressDialog(貌似现在已经取消使用了)

与AlertDialog类似,但是会有一个进度条,一般表示当前操作比较耗时,让用户耐心等待

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

@Override

public void onClick(View v) {

Switch (v.getId()) {

case R.id.button: {

ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);

progressDialog.setTitle(“This is ProgressDialog”);

progressDialog.setMessage(“Loading…”);

progressDialog.setCancelable(ture);

progressDialog.show();

}break;

default:

break;

}

}

设置setCancelable()传入false,表示不能通过back键取消掉。

3.3 详解4种基本布局。(新的测试项目UILayoutTest)

布局的内部除了放置控件外,还可以放置布局,通过多层布局的嵌套,我们就能够完成一些比较复杂的界面实现。

Android中包括四种布局:线性布局、相对布局、帧布局、百分比布局

3.3.1 线性布局

线性布局LinearLayout,将他所包含的控件在线性方向上依次排列。之前我们用的一直都是这个线性布局,可以通过修改adroid:orientation属性指定排列方向是vertival为垂直方向,horizontal为水平方向。

<LinearLayout xmlns:android:=“http://schemas.android.com/apk/res/android

android:orientation=“vertical”/horizontal

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

</ LinearLayout>

需要注意的是,如果排列方式设置为horizontal水平方向,内部的控件就绝对不能将宽度指定为match_parent,因为这样的话,单独一个控件就占满整个水平方向了,其他控件就没有位置可以放置了。同样道理,如果排列方向设置为vertival垂直方向,那么内部控件的高度就不能设置为match_parent。

android:layout_gravity属性用于指定文字在控件中的对齐方式。

Android:layout_weight使用比例的方式来指定控件的大小(在手机屏幕的适配性方便非常重要)

案例:

<EditText

android:id=“@+id/input_message”

android:layout_width=“0dp”

android:layout_height=“wrap_content”

android:layout_weight=“1”

android:hint=“Type something” />

<Button

android:id=“@+id/send”

android:layout_width=“0dp”

android:layout_height=“wrap_content”

android:layout_weight=“1”

android:text=“Send” />

这里虽然设置了android:layout_width=“0dp”,但是由于设置了比重,以比重为准,0dp是比较标准的写法。

比重就是总数加起来,然后再根据占的比重来决定宽度,现在很明显是EditText和Button平分宽度。

这时候如果Button的layout_width改为wrap_content,然后EditText保持1的比比重不变,那么Button就会保持固定大小,然后EditText占据剩余的宽度空间。

3.3.2 相对布局

相对布局RelativeLayout,相对随意,它可以通过相对定位的方式让控件出现在布局的任何位置。因此,属性非常多,不过这些属性都是有规律可循。

如案例:5个按钮,分别在左上,右上,中间,左下,右下。

<RelativeLayout xmlns:android:”http://schemas.android.com/apk/res/android

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<Button

android:id=“@+id/button1”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignParentLeft=“true”

android:layout_alignParentTop=“true”

android:text=“Button 1” />

<Button

android:id=“@+id/button2”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignParentRight=“true”

android:layout_alignParentTop=“true”

android:text=“Button 2” />

<Button

android:id=“@+id/button3”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_centerInParent=“true”

android:text=“Button 3” />

<Button

android:id=“@+id/button4”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignParentLeft=“true”

android:layout_alignParentBottom=“true”

android:text=“Button 4” />

<Button

android:id=“@+id/button5”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignParentRight=“true”

android:layout_alignParentBottom=“true”

android:text=“Button 5” />

</RelativeLayout>

上面是相对于父控件进行定位的。

如果使用相对于其他控件的相对布局,用下面的方式

android:layout_above=“@id/button3” 上方

android:layout_toRightOf=“@id/button3” 右侧

android:layout_toLeftOf=“@id/button3” 左侧

android:layout_below=“@id/button3” 下侧

android:layout_alignLeft 让一个控件的左边缘和另一个控件的左边缘对齐

android:layout_alignRight 让一个控件的右边缘和另一个控件的右边缘对齐

android:layout_alignTop 上边缘对齐

android:layout_alignBottom 下边缘对齐

3.3.3 帧布局

帧布局FrameLayout,相对其他两种布局简单多了,应用场景也少了很多。

这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上角

<FrameLayout xmlns:android=“http://schemas.android.com/apk/res/android

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<TextView

android:id=“@+id/text_view”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:text=“This is TextView” />

<ImageView

android:id=“@+id/image_view”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:src=“@mipmap/ic_launcher” />

</FrameLayout>

FrameLayout还可以使用layout_gravity属性

Android:layout_gravity=“left”

Android:layout_gravity=“right”

由于FrameLayout的定位方式的缺失,导致它的使用场景比较少。

3.3.4 百分比布局

前面3种布局都是从android 1.0版本就开始支持了,一直沿用到现在。这种布局中,我们可以不再使用wrap_content、match_parent等方式来指定控件的大小,而是允许直接指定控件在布局中所占的百分比。

百分比布局只为FrameLayout和RelativeLayout进行了功能拓展,提供了PercentFrameLayout和PercentRelativeLayout这两种全新的布局。

怎样做到新增的百分比布局在所有的android版本中都能使用呢。为此,android团队将百分比布局定义在support库当中。我们只需要在项目中build.gradle中添加百分比布局库的以来,就能保证百分比布局在android所有系统版本上的兼容性了。

修改app/build.gradle文件中

Dependencies {

implementation ‘com.android.support:percent:28.0.0’

}

如果发现28.0.0版本不够新,系统会提示你修改。修改完之后还需要重新build一下。(点击Sync Now)

现在使用PercentFrameLayout

app:layout_widthPercent=“50%” // 将宽度指定为布局的50%

app:layout_heightPercent=“50%” // 将高度指定为布局的50%

PercentFrameLayout还是会继承FrameLayout的特性,即所有的控件默认都摆放在布局的左上角,为了让四个按钮不会重叠,还是接住了layout_gravity分别将4个按钮放置在左上、右上、左下、右下四个位置。android:layout_gravity="left|bottom"

PercentRelativeLayout的用法类似,也是继承了RelativeLayout的所有属性,然后再结合app:layout_widthPercent和app:layout_heightPercent来按百分比指定控件的宽高

3.4 创建自定义控件

所有控件都是继承自View,所有的布局都是直接或者间接继承自ViewGroup的。

创建一个新的应用UICustomViews

单独创建一个title.xml,用于设定类似iOS的顶部导航栏,然后在activity_main.xml中添加<include layout=“@layout/title” />

android:background=“@drawable/title_bg.png” // 用于指定背景图片

Android:layout_marginLeft,layout_marginRight,layout_mariginTop,layout_marginBottom用于指定上下左右偏移的距离

android:background可以指定一个背景图片.

android:layout_margin属性可以指定控件在上下左右方向上偏移的距离,当然也可以使用

android:layout_marginLeft或者android:layout_marginTop等属性来单独指定控件在某个方向上便宜的距离。

接下来修改activity_main.xml

加上<include layout=“@layout/title” />

最后在MainActivity中将系统自带的标题栏隐藏掉

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();

}

}

}

ActionBar就是我们的导航栏

隐藏标题栏之后,我们自己写的tilte.xml就生效了。以后只要一句include就可以包含进来了。

3.4.2 创建自定义控件

新建TitleLayout继承自LinearLayout,让它成为我们自定义的标题栏控件

public class TitleLayout extends LinearLayout {

public TitleLayout(Context context, AttributeSet attrs) {

super(context, attrs);

LayoutInflater.from(context).inflate(R.layout.title, this);

}

}

在布局中引入TitleLayout控件就会调用这个构造函数,然后在构造函数中需要对标题栏布局进行动态加载,这就要借助LayoutInflater来实现。通过LayoutInflater的from()方法可以构建出一个ayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件,inflate()方法两个参数,第一个是加载的布局文件的id,这里传入R.layout.title,第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this。

现在这个自定义控件创建好了,然后我们需要在布局文件中添加这个自定义控件,修改activity_main.xml中的代码,如下所示:

<LinearLayout xmlns:android=“http”//schemas.android.com/apk/res/android

android:layout_width=“match_parant”

android:layout_height=“match_parent” >

// 包名在这里是不可以省略的

<com.example.uicustomviews.TitleLayout

android:layout_width=“match_parent”

android:layout_height=“wrap_content” />

</LinearLayout>

下面我们尝试为标题栏中的按钮注册点击事件,修改TitleLayout中的代码,如下

public class TitleLayout extends LinearLayout {

public TitleLayout(Context context, AttributeSet attrs) {

super(context, attrs);

LayoutInflater.from(context).inflate(R.layout.title, this);

Button titleBack = (Button) findViewById(R.id.title_back);

Button titleEdit = (Button) findViewById(R.id.title_edit);

titleBack.setOnclickListener(new OnClickListener() {

@Override

public void onClick(View v) {

((Activity) getContext()).finish();

}

});

titleEdit.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

Toast.makeText(getContext(), “You click Edit button”, Toast.LENGTH_SHORT).show();

}

});

}

}

首先还是通过findViewById()方法得到按钮的实例,然后分别调用setOnclickListenner()方法给两个按钮注册了点击事件,

当点击返回按钮的时候销毁当前的活动

当点击编辑按钮的时候弹出一段文本。

3.5 最常用和最难用的空控件 —》 ListView(相当于iOS中的UITableView)

3.5.1 LIstView的简单用法

新建一个项目ListViewTest项目,然后修改activity_main.xml中的代码

<LinearLayout xmlns:android=android=“http://schemas.android.com/apk/res/android

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<ListView android:id=“@+id/list_view”

android:layout_width=“match_parent”

android:layout_height=“match_parent” />

</LinearLayout>

接下来修改MainActivity的代码

public class MainActivity extends AppCompatActivity {

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<String> adapter = new ArrayAdapter<String>(

MainActivity.this, android.R.layout.simple_list_item_1, data);

ListView listView = (ListView) findViewById(R.id.list_view);

listView.setAdapter(adapter);

}

}

我们需要通过借助适配器来把数据传递给ListView,Android中提供了很多适配器的实现类,其中我认为最好用的就是ArrayAdapter。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。ArrayAdater有多个构造函数的重载,你应该根据实际情况选择最合适的一种。上面在ArrayAdapter的构造函数中依次穿日当前上下文、ListView子项布局的id,以及要适配的数据。注意,我们使用了android.R.layout.simple_list_item_1作为子项布局的id,这是一个Android内置的布局文件,里面只有一个TextView,可用于简单的显示一段文本。

3.5.2 定制ListView的界面

只显示一段文本的LIstView太单调了,现在我们来对ListView的界面进行定制。

首先我们需要准备好一组图片,分别对应提供的每一种水果。

接着定义一个实体类,作为ListView适配器的适配类型。新建Fruit类,代码如下:

public class Fruit {

private String name;

private int imageId;

public Fruit(String name, int imageId) {

this.name = name; // 水果名

this.imageId = imageId; // 水果资源id

}

public String getName() {

return name;

}

public int getImageId() {

return imageId;

}

}

然后我们为ListVie的子项指定一个我们自定义的布局,在layout目录下新建fruit_item.xml,代码如下

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<ImageView

android:id=“@+id/fruite_image”

android:layout_width=“wrap_content”

android:laytout_height=“wrap_content” />

<TextView

android:id=“@+id/fruit_name”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_gravity=“center”

android:layout_marginLeft=“10dp” />

</LinearLayout>

上面的代码中,我们定义了一个ImageView用于显示水果的图片,又定义了一个TextView用于显示水果的名称,并让TextView在垂直方向上居中显示。

接下来我们创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类,新建类FruitAdapter:

public class FruitAdapter extends ArrayAdapter<Fruit> {

private int resourceId; // 图片资源id

public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {

super(context, textViewResourceId, objects);

resourceId = textViewResoureceId;

}

@Override

public View getView(int positon, VIew convertView, ViewGroup parent) {

Fruit fruit = getItem(position); // 获取当前项的Fruit实例

View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, flase);

ImageView fruitImage = (ImageView) findViewById(R.id.fruit_image);

TextView fruitName = (TextView) findViewById(R.id.fruit_name);

fruitImage.setImageResource(fruit.getImageId());

fruitName.setText(fruit.getName());

return View;

}

}

FruitAdapter重写了父类的一组构造函数,用于将上下文,ListView子项布局的id和数据传递过去。另外又重写了getVIew()方法,这个方法在每一个子项被滚动到屏幕内的时候会被调动。在getView()方法中,首先通过getItem()方法得到当前项的Fruit实例,然后使用LayoutInflater来为这个子项加载我们传入的布局。

这里的Layouinflater的inflater()方法接收3个参数,前面两个参数我们都知道是什么意思了,第三个参数指定成flase,表示只让我们在父布局中声明的layout属性生效,但不为这个View添加父布局,因为一旦View有了父布局之后,它就不能再天机到ListView中了。(现在不太明白没关系,这其实是ListView中的标准写法)

最后我们修改MainActivity中的代码,

public class MainActivity extends AppCompatActivity {

private List<Fruit> 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);

}

private void initFruit() {

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.apple_pic);

fruitList.add(banana);

}

}

}

只要修改fruit_item.xml中的内容,就可以定制出各种复杂的界面了。

3.5.3 提升ListView的运行效率

目前我们的LIstView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。

仔细观察,getView()方法还有一个converView的参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。修改FruitAdapter中的代码,如下

public class FruitAdapter extends ArrayAdapter<Fruit> {

@Override

public View getView(int positon, View convertView, VIewGroup parent) {

Fruit fruit = getItem(position);

View view;

if (convertView == null) {

view = LayoutFlater.from(getContext()).inflate(resourceId, parent, false);

} else {

view = convertView;

}

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()的时候,还是会调用View的findViewById()方法来获取一次控件的实例。继续优化,使用ViewHolder来使得不会重复创建。

public View getView(int position, View convertView, ViewGroup parent) {

Fruit fruit = getItem(position);

View view;

ViewHolder viewHolder;

if (convertVIew == null) {

view = LayoutFlater.from(getContext()).inflate(resourceId, parent, false);

viewHolder = new ViewHolder();

veiwHolder.fruitImage = (ImageView)view.findViewById(R.id.fruit_image);

viewHolder.fruitName = (TextView)view.findViewById(R.id.fruit_name);

view.setTag(viewHolder); // 将ViewHolder存储在View中。

} else {

view = convertView;

viewHolder = (ViewHolder)view.getTag();

}

viewHolder.fruitImage.setImageResource(fruit.getImageId());

viewHolder.fruitNma.setText(fruit.getName());

return view;

}

class ViewHolder {

ImageView fruitImage;

TextView fruitName;

}

3.5.4 ListView的点击事件

修改MainActivity中的代码

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

@Override

public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

Fruit fruit = fruitList.get(position);

Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();

}

});

3.6 更强大的滚动控件 — RecyclerView

ListView虽然很好用,扩展性不好,只能实现纵向的滚动效果,而且如果我们不使用一些技巧来提升它的运行效率,那么LIstView的性能就会非常差。

RecyclerView是增强版的ListView,可以轻松实现ListView的效果,还优化了ListView的各种不足之处。目前Android更推荐使用RecyclerView。

新建一个RecyclerViewTest的项目

android团队将RecyclerView定义在support库当中,想要使用RecyclerVIew这个控件,需要再项目中添加相应的依赖库才行。

打开app/build.gradle文件,在dependencies中添加如下内容

dependencies {

compile fileTree(dir: ‘libs’, include: [‘*.jar’])

compile ‘com.android.support:appcompat-v7:24.2.1’

compile ‘com.android.support:recyclerview-v7:24.2.1’

testCompile ‘junit:junit:4.12’

}

Warming:

新版本配置如下:recyclerview的版本应该和appcompat的版本保持一致。

dependencies {

implementation fileTree(dir: 'libs', include: ['.jar'*])

implementation 'androidx.appcompat:appcompat:1.0.2'

implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

implementation 'androidx.recyclerview:recyclerview:1.1.0-beta02'

testImplementation 'junit:junit:4.12'

androidTestImplementation 'androidx.test:runner:1.2.0'

androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

}

添加完之后点击Sync Now来进行同步一下,然后修改activity_main.xml中的代码,

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

// 由于RecyclerView并不是在系统SDK中,所以需要把完整的包名路径写出来

<android.support.v7.widget.RecyclerView

android:id=“@+id/recycler_view”

android:layout_width=“match_parent”

android:layout_height=“match_parent” />

</LinearLayout>

为了使得RecyclerView达到和ListView相同的效果。我们需要把图片。Fruit类,fruit_item.xml复制过来。免得再写一遍。

新建一个新的FruitAdapter,继承自RecyclerVIew.Adapter,并将泛型指定为FruitAdapter.ViewHolder

。。。

3.6.2 RecyclerView实现横向滚动和瀑布流布局

首先对fruit_item.xml进行修改,因为这个布局目前是水平排列的。

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android

android:orientation=“vertical”

android:layout_width=“100dp”

android:layout_height=“wrap_content” >

// 垂直方向排列

<ImageView android:id=“@+id/fruit_image”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_gravity=“center_horizontal” />

// 垂直方向排列

<TextView android:id=“@+id/fruit_name”

android:layout_width=“wrap_content”

android:layotu_height=“wrap_content”

android:layout_gravity=“center_hotizontal”

android:layout_marginTop=“10dp” />

</LinearLayout>

现在修改MainActivity的代码

public class MainActivity extends AppCompatActivity {

private List<Fruit> fruitList = new ArrayList<>();

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(saveInstanceState);

setContentView(R.layout.activity_main);

initFruit();

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

LinearLayoutManager layoutManager = new LinearLayoutManager(this);

layoutManager.setOrientaion(LinearLayoutManager.HORIZONTAL);

recyclerView.setLayoutManager(layoutManager);

FruitAdapter adapter = new FruitAdapter(fruitList);

recyclerView.setAdapter(adapter);

}

}

调用LinearLayoutManger的setOrientation()方法来设置布局的排列方向,默认是纵向排列的,我们传入LinearLayoutManger.HORIZONTAL表示让布局横行排列。

ListView的布局排列是由自身去管理的,而RecyclerView则将这个工作交给了LayoutManger,LayoutManage中制定了一套壳拓展的布局排列接口,子类只要按照接口的规范来实现,就能制定出各种不同排列方式的布局了。

处理LinearLayoutManger,RecyclerView还给我们提供了GridLayoutManger和StaggeredGridLayoutManager这两种内置的布局排列方式。GridLayoutManaget可以用于实现网格布局,StaggeredGridLayoutManager可以用于实现瀑布流布局。

现在我们来实现更加炫酷的瀑布流布局(相当于iOS中的UICollectionVeiw)

继续修改fruit_item.xml

<LinearLayout xmlns:android=“http://schemas.adnroid.com/apk/res/android

adnroid:orientation=“vertical”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:layout_margin=“5dp” >

<ImageView

android:id=“@+id/fruit_image”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_gravity=“center_horizontal” />

<TextView

android:id=“@+id/fruit_name”

android:layout_width=“wrap_contetn”

android:layout_height=“wrap_contetn”

android:layout_gravity=“Left”

android:layout_marginTop=“10dp” />

</LinearLayout>

这里做了一些小调整,首先将LinearLayout的宽度由100dp修改为match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的。而不是一个固定的值。另外我们使用了layout_maring属性来让子项之间互留一点间距。还有将TextView的对齐属性改成了居左对齐。

接着修改MainAcitivity代码:

public class MainActivity extends AppCompatActivity {

private List<Fruit> fruitList = new ArrayList<>();

@Override

protected void onCreate(Bundel savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main):

initFruits();

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

// 构造函数接收两个参数,3表示布局分为3列,第二个参数用于指定布局的排列方式。

StaggerdGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTCAL);

recyclerView.setLayoutManager(layoutManager);

FruitAdapter adapter = new FruitAdapter(fruitList);

recyclerView.setAdapter(adapter);

}

private void initFruit() {

for (int i = 0; i < 2; i++) {

Fruit apple = new Fruit(getRandomLengthName(“Apple”, R.drawable.apple_pic);

fruitList.add(apple);

}

}

private Strring getRandomLengthName(String name) {

Random random = new Random();

// 创建一个1-20的随机数,

int length = random.nextInt(20) + 1;

StringBuilder builder = new StringBuilder();

for (int i = 0; i < length; i++ ) {

builder.append(name);

}

// 输出字符串

return builder.toString();

}

}

3.6.3 RecyclerView的点击事件

不同于ListView,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法。而是需要我们自己给子项具体的view去注册点击事件,相对于ListView来说,实现起来要复杂一点。

ListView的点击事件上的处理并不人性化,setOnItemClickListener()方法注册的是子项的点击事件,但如果我们想点击的是子项里面具体的某一个按钮呢,ListView也能实现,但是实现起来就相对比较麻烦了。为此,RecyclerView干脆直接摒弃了子项点击事件的监听器,所有的点击事件都由具体的View去注册,就再没有这个困扰了。

修改FruitAdapter中的代码:

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {

private List<Fruit> mFruitList;

static class ViewHolder extends RecyclerView.ViewHolder {

View fruitView;

ImageView fruitImage;

TextView fruitName;

public ViewHolder(View view) {

super(view);

fruitView = view;

fruitImage = (ImageView) view.findViewById(R.id.fruit_image);

fruitName = (TextView) view.findViewById(R.id.fruit_name);

}

}

public FruitAdapter(List<Fruit> fruitList) {

mFruitList = fruitList;

}

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);

final ViewHolder holder = new ViewHolder(view);

holder.fruitVeiw.setOnClickListener( new View.OnClickListener() {

@Ovrride

public void onClick(View v) {

int position = holder.getAdapterPosition();

Fruit fruit = mFruitList.get(position);

Toast.makeText(v.getContext(), “you clicked view” + fruit.getName(), Toast.LENTH_SHORT()).show();

}

)};

holder.fruitImage.setOnclickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

int position = holder.getAdapterPositon();

Fruit fruit = mFruitList.get(position);

Toast.makeText(v.getContext(), “you clicked view” + fruit.getName(), Toast.LENTH_SHORT).show();

}

)};

return holder;

}

}

RecyclerView的强大之处在于,它可以轻松实现子项中任意控件或布局的点击事件。

3.7 编写界面的最佳实践。

现在我们结合之前所有的知识,来做衣蛾较为复杂且没管的聊天界面。创建一个UIBestPractice项目。

具体代码看Android Studio里面的代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容