美文网首页
第三章 UI

第三章 UI

作者: 秋山君 | 来源:发表于2017-03-09 23:39 被阅读41次

    第二章 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的命名空间,这样才能使用百分比布局的自定义属性。
    <android.support.percent.PercentFrameLayout
        xmlns:android="……"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width=……><!--定义app命名空间-->
        
        <Button android:id="@+id/button1"
            android:layout_gravity="left|bottom"
            app:layout_widthPercent=50%
            app:layout_heightPercent=50%/>
            
        <Button android:id="@+id/button2"
            android:laytout_gravity="right|bottom"
            app:layout_widthPercent=50%
            app:layout_heightPercent=50%/>
    </android.support.percent.PercentFrameLayout>
    

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

    3.4 创建自定义控件 P109

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

    3.4.1 引入布局

    <include layout="layout/title"/>
    
    • 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中,使用自定义控件的完整类名(包名不可省略),就可添加该控件。
    <com.example.man.test.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">    
    </com.example.man.test.TitleLayout>
    

    3.5 ListView P113

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

    3.5.1 ListView的简单用法

    1. 添加控件
    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    
    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<String> arrayAdapter = new ArrayAdapter<String>(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布局文件
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <ImageView
            android:id="@+id/fruit_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/fruit_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_gravity="center_vertical"/>
    </LinearLayout>
    

    3.新建一个FruitAdapter类

    public class FruitAdapter extends ArrayAdapter<Fruit> {
        private int resourceid;
    
        public FruitAdapter(Context context, int resource, List<Fruit> 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<Fruit> 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:
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    
    1. 新建RecyclerViewAdapter类:
    public class FruitRAdapter extends RecyclerView.Adapter<FruitRAdapter.ViewHolder> {
        private List<Fruit> 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<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);
            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<FruitRAdapter.ViewHolder> implements View.OnClickListener {
        private List<Fruit> 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

          本文链接:https://www.haomeiwen.com/subject/bqlmgttx.html