第三章 UI

第二章 UI

标签(空格分隔): 未分类


疑问

  1. 3.5.2,inflate()的第三个参数false是起什么作用?为什么不添加父布局?[定位](#3.5.2 定制ListView的界面)
  2. 能否写出通用的RecyclerViewAdapter和ViewHolder?

知识结构

3.2 常用控件/组件的使用方法 P77

3.2.1 TextView

  • 文字默认为居左上角对齐,通过android:gravity来指定文字的对齐方式。
  • 对控件的高和宽指定固定值会造成适配的问题。

3.2.2 Button

  • 系统会对Button中的所有英文字母自动进行大写转换,可以通过android:textAllCaps="false"来禁止。
  • 为点击事件注册监听器的方法:①匿名类;②实现接口。实现接口,可以集中管理点击事件的逻辑。

3.2.3 EditText

  • 使用android:hint属性来指定一段提示性文字。
  • 使用android:maxLines属性来指定EditText的最大行数,避免EditText过分拉长。

3.2.4 ImageView

图片通常都存放在以“drawable”开头的不同分辨率的目录下。

  • 使用android:src属性来指定图片的地址。
  • 使用setImageResource()来改变显示的图片。

3.2.5 ProgressBar

Android所有控件均有可见属性,可通过android:visibility属性进行指定,它有三个取值,visible、invisible、gone。还可以通过setVisibility()方法来设置可见性,可以传入View.VISIBILE、View.INVISIBLE、View.GONE三个值

  • 通过style属性来指定多种进度条。
  • 通过android:max来指定progress的最大值。
  • 通过setProgress()getProgress()方法来操作progress值。

3.2.6 AlterDialog

使用方法如下所示:

AlterDialog.Builder dialog = new AlterDialog.Builder(MainActivity.this);
dialog.setTitle("this is title");
dialog.setMessage("this is message");
dialog.setCancelable(false);
dialog.setPositiveButton("Ok", new DialogInterface.OnClickListener(){
    @Override                
    public void onClick(DialogInterface dialog, int which) {
        //do something
    }
});
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
         //do something
    }
});
dialog.show();

3.2.7 ProgressDialog

使用方法和AlertDialog较为相似。

注意:在setCancelable()中传入false,则无法使用Back键退出。当数据加载完,必须调用progressDialog的dismiss()方法来关闭对话框。

3.3 详解4种基本布局 P94

3.3.1 LinearLayout

  • android:orientation用于指定控件的排列顺序。默认为horizontal,水平排列;vertical,水平排列。按照控件在布局代码中的位置依次排列。
  • android:layout_gravity用于指定控件在布局中的对齐方式;android:gravity用于指定文字在控件中的对齐方式。

注意:当LinearLayout的排列方式为horizontal时,它所包含的view的android:layout_gravity只能为vertical。同理当LinearLayout的排列方式为vertical时,android:layout_gravity只能为horizontal.

  • android:layout_weight属性允许使用比例的方式来指定控件的大小。只有在LinearLayout中才可以使用这个属性。

    以下是规范写法:

android:layout_width="0dp"
android:layout_weight="1"

3.3.2 RelativeLayout

1.相对父布局定位
android:layout_alignParentRight
android:layout_alignParentLeft
android:layout_alignParentTop
android:layout_alignParentBottom
android:layout_centerInParent
2.相对控件定位
android:layout_above
android:layout_below
android:layout_toRightOf
android:layout_toLeftOf
android:layout_alignLeft
android:layout_alignRight
android:layout_alignTop
android:layout_alignBottom

注意:当一个控件引用另一个控件时,该控件一定要定义在另一个控件的后面,否则会出现找不到id的情况

3.3.3 FrameLayout

所有控件默认摆放在布局的左上角。也可以使用android.layout_gravity属性来指定控件在布局中的对齐方式。因为定位方式存在缺陷,所以使用场景较少。

3.3.4 百分比布局

只有在LinearLayout中才可以使用android.layout_weight属性。为了能够按比例指定控件大小,百分比布局为RelativeLayout、FrameLayout提供了PercentRelativeLayout、PercentFrameLayout两个全新的布局。

使用方法
  1. 打开app/build.gradle文件,在dependencies闭包中添加百分比布局依赖。
compile 'com.android.support:percent:24.2.1'
  1. 在布局文件中,先写出最外层完整的包路。还要再定义一个app的命名空间,这样才能使用百分比布局的自定义属性。

    
    

注意:在上面的代码中,能使用app的前缀就是因为刚才定义了app的命名空间。我们能一直使用android前缀的属性也是同样的道理。

3.4 创建自定义控件 P109

View是Android中一种最基本的组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域上的各种事件。
ViewGroup是一种特殊的View,它是一个用于放置控件和布局的容器,它可以包含多个子View和子ViewGroup。
所有控件都是直接或间接继承自View,所有布局都是直接或间接继承自ViewGroup。

3.4.1 引入布局


  • android:padding规定控件与父View的距离;
  • android:layout_margin规定控件与其它(上下左右)View之间的距离。
如何隐藏系统自带的标题栏。P111
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        android.support.v7.app.ActionBar actionBar = getSupportActionBar();
        if (actionBar != null){
            actionBar.hide();
        }
    }

3.4.2 创建自定义控件

  1. 创建自定义控件。先创建一个布局B(TitleLayout)的,在该类中加载3.4.1中创建的布局A(title),并将B设为A的父布局。再为其中的各个控件添加事件以及功能。
public class TitleLayout extends LinearLayout implements View.OnClickListener{
    public TitleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title,this);//加载3.4.1中创建的布局A(title),并将B(TitleLayout)设为A的父布局
        Button button_titleBack = (Button)findViewById(R.id.title_back);
        Button button_titleEdit = (Button)findViewById(R.id.title_edit);
        button_titleBack.setOnClickListener(this);
        button_titleEdit.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.title_back:
                ((Activity)getContext()).finish();
                break;
            case R.id.title_edit:
                Toast.makeText(getContext(),"nihao",Toast.LENGTH_SHORT).show();
        }
    }
}

①LayoutInflater.from(context).inflate(R.layout.title,this);
通过LayoutInflaer的from()可以构建出一个LayoutInflater对象,然后调用inflate()动态加载一个布局文件。inflate()方法的第一个参数是要加载的布局文件的id,第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitileLayout,于是直接传入this。
②((Activity)getContext()).finish();
其实就是从Activity传了个Context过来,不过因为不只Activity有Context,比如Service也有,所以加了个强制类型转化。而getContext()得到的是this,就相当于this.finish(),其实一般我们在Activity里直接finish()是一种简写。

  1. 使用自定义控件。在布局C中,使用自定义控件的完整类名(包名不可省略),就可添加该控件。
    

3.5 ListView P113

==ListView==:允许用户通过手指上下滑动的方式,将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据会滚动到屏幕外。

3.5.1 ListView的简单用法

  1. 添加控件

  1. 新建一个Adapter,将context、子项(Item)的样式和data传入进去。

数组中的数据无法直接传递给ListView,需要借助适配器。ArrayAdapter可以通过泛型来指定要匹配的数据类型。

  1. 调用setAdapter()为ListView添加Adapter.
private String[] data = {"apple","orange","watermelon","pear","grape","pineapple","strawberry","cherry","mango","banana","apple","orange","watermelon","pear","grape","pineapple", "strawberry","cherry","mango","banana"};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main3);
    ArrayAdapter arrayAdapter = new ArrayAdapter(Main3Activity.this,android.R.layout.simple_list_item_1,data);
    ListView listView = (ListView)findViewById(R.id.listview);
    listView.setAdapter(arrayAdapter);
}

3.5.2 定制ListView的界面

  1. 新建一个Fruit类
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;
    }
}
  1. 新建一个fruit_item布局文件


    
    

3.新建一个FruitAdapter类

public class FruitAdapter extends ArrayAdapter {
    private int resourceid;

    public FruitAdapter(Context context, int resource, List objects) {
        super(context, resource, objects);
        resourceid=resource;
    }

    @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 frutiName = (TextView) view.findViewById(R.id.fruit_name);
        frutiName.setText(fruit.getName());
        fruitImage.setImageResource(fruit.getImageId());
        return view;
        //return super.getView(position, convertView, parent);AS自动生成的返回值,但是在这个例子中运行会报错
    }
}

①getView()在每一个子项被滚动到屏幕内时会被调用。
②LayoutInflater.from(getContext()).inflate(resourceid,parent,false);第三个参数指定为false,表示只让我们在父布局中声明的layout属性生效,但不为view添加父布局。因为一旦view有了父布局之后,它就不能添加到ListView中了。

  1. 将context、fruit_item布局和数据传入FruitAdapter,调用setAdapter()为ListView添加Adapter.
private String[] data = {"apple","orange","watermelon","pear","grape","pineapple","strawberry","cherry","mango","banana","apple","orange","watermelon","pear","grape","pineapple", "strawberry","cherry","mango","banana"};
private List fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main3);
    initFruits();
    ListView listView = (ListView)findViewById(R.id.listview);
    FruitAdapter fruitAdapter = new FruitAdapter(Main3Activity.this,
            R.layout.fruit_item,fruitList);
    listView.setAdapter(fruitAdapter);
}

private void initFruits(){
    for (String f :
            data) {
        Fruit fruit = new Fruit(f,R.drawable.ic_launcher);
        fruitList.add(fruit);
    }
}

3.5.3 提升ListView的运行效率

  • 每次调用getView()时都会加载一遍布局,影响效率。
    解决方法:convertView参数可以将加载的布局进行缓存,以便之后可以重用。
  • 每次调用getView()时都会获取一次控件的实例,影响效率。
    解决方法:新建一个ViewHolder类来缓存控件实例。
    @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.fruitName =(TextView) view.findViewById(R.id.fruit_name);
            viewHolder.fruitImage =(ImageView) view.findViewById(R.id.fruit_image);
            view.setTag(viewHolder);
        }else {
            view = convertView;
            viewHolder =(ViewHolder) view.getTag();
        }
        viewHolder.fruitName.setText(fruit.getName());
        viewHolder.fruitImage.setImageResource(fruit.getImageId());
        return view;
    }

    class ViewHolder{
        ImageView fruitImage;
        TextView fruitName;
    }

3.5.4 ListView的点击事件

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView parent, View view, int position, long id) {
        Fruit fruit = fruitList.get(position);
        Toast.makeText(Main3Activity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
    }
});

注意:
Fruit fruit = getItem(position);只能在Adapter内部使用。
Fruit fruit = fruitList.get(position);则是在声明fruitList的那个类中使用,这里也就是Main3Activity。

3.6 ListView的增强版RecyclerView

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

3.6.1 RecyclerView的基本用法

  1. 打开app/build.gradle文件,在dependenceies闭包中添加远程依赖:
compile 'com.android.support:recyclerview-v7:24.2.1'
  1. 在布局文件中,用完整的包路径添加RecyclerView:

  1. 新建RecyclerViewAdapter类:
public class FruitRAdapter extends RecyclerView.Adapter {
    private List mFruitList;
    static class ViewHolder extends RecyclerView.ViewHolder{
         ImageView fruitImage;
         TextView fruitName;
        public ViewHolder(View itemView) {
            super(itemView);
            fruitImage = (ImageView)itemView.findViewById(R.id.fruit_image);
            fruitName = (TextView)itemView.findViewById(R.id.fruit_name);
        }
    }

    public FruitRAdapter(List fruitList) {
        mFruitList = fruitList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,parent,false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.fruitImage.setImageResource(mFruitList.get(position).getImageId());
        holder.fruitName.setText(mFruitList.get(position).getName());
    }

    @Override
    public int getItemCount() {
        return mFruitList.size();
    }
}
  • onCreateViewHolder()用于加载子项(item)布局,创建ViewHolder实例,将布局中的控件实例缓存到ViewHolder中。
  • onBindViewHolder()用于为ViewHolder中的实例设置属性。
  • gerItemCount()用于返回RecyclerView中item的数量。
  1. 创建RecyclerViewAdapter的实例,并将context、item布局、data传递进去。

  2. 为RecycleerView添加Adapter。

    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(layoutManager);
    FruitRAdapter fruitRAdapter = new FruitRAdapter(fruitList);
    recyclerView.setAdapter(fruitRAdapter);
    

    LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局,可以实现和ListView类似的效果。

3.6.2 实现横向滚动和瀑布流

实现横向滚动
  1. 修改item布局。LinearLayout改成垂直方向排列,使其适合横向滚动要求。

  2. 在[3.6.1](#3.6.1 RecyclerView的基本用法)的基础上,调用LinearLayout的setOrientation()来设置布局的排列方向,默认是纵向排列

layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
实现瀑布流
  1. 修改item布局。LinearLayout的宽度改为match_parent,因为瀑布流布局中item的宽度是根据布局的列数自动适配的,不是一个固定值。

  2. LinearLayoutManager改为StaggeredGridLayoutManager。StaggeredGridLayoutManager()的第一个参数用于指定布局的列数,第二个参数用于指定布局的排列方向。

    StaggeredGridLayoutManager layoutManager = new
                    StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
    

同理实现网格布局也只需要将LinearLayoutManager改为GridLayoutManager。GridLayoutManager()的第一个参数是context,第二个是网格列数(spanCount)。

3.6.3 RecyclerView的点击事件

ListView的点击事件是对整个子项(item)注册了点击监听器,而RecyclerView为了能对item中的所有view添加监听器,舍弃了子项点击事件的监听器,所有的点击事件都由具体的View去注册。

public class FruitRAdapter extends RecyclerView.Adapter implements View.OnClickListener {
    private List mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder{
        View fruitView;//添加了子项的view
        ImageView fruitImage;
        TextView fruitName;
        public ViewHolder(View itemView) {
            super(itemView);
            fruitView = itemView;
            fruitImage = (ImageView)itemView.findViewById(R.id.fruit_image);
            fruitName = (TextView)itemView.findViewById(R.id.fruit_name);
        }
    }

    @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.fruitView.setOnClickListener(this);//为item和image分别注册了点击监听器
        holder.fruitImage.setOnClickListener(this);
        return new ViewHolder(view);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.fruit_view:
                Toast.makeText(v.getContext(),"view",Toast.LENGTH_SHORT).show();
                break;
            case R.id.fruit_image:
                Toast.makeText(v.getContext(),"image",Toast.LENGTH_SHORT).show();
                break;
        }
    }
    ...
}

3.7 编写界面的最佳实践

3.7.1 制作Nine-Patch图片 P133

3.7.2 编写精美的聊天界面

  1. 创建一个message类、msg_item.xml、MsgRecyclerViewAdapter类
  2. 新建一个MainActivity,在布局中添加RecyclerView、Button、EditText。
  3. 在MainActivity中使用RecyclerView。

你可能感兴趣的:(第三章 UI)