Android Studio —— UI

284 阅读6分钟

第一行代码读书笔记

常用控件

TextView

<TextView
    android:id="@+id/text_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="24sp"
    android:textColor="#00ff00"
    android:text="This is TextView"/>
  • layout_width&layout_height
    • Android中的所有控件都具有这两个属性
    • match_parent/fill_parent/wrap_content
  • gravity
    • 指定文字的对齐方式
    • top/bottom/left/right/center...
    • 可以用|来同时指定多个值
    • center == center_vertical | center_horizontal
  • textSize
    • 指定文字的大小
    • 使用sp作为单位
  • textColor
    • 指定文字的颜色
  • etc

Button

<Button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Button"
    android:textAllCaps="false"/>
  • textAllCaps
    • 所有英文字母自动转换为大写
@Override
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) {
            // TODO
        }
    });
}
  • 在onCreate()中为Button点击事件注册监听器
  • 匿名类方式
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:
                // TODO
                break;
            default:
                break;
        }
    }
}
  • 用实现接口的方式注册监听器

EditText

<EditText
    android:id="@+id/edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Type something here"
    android:maxLines="2"
    />
  • hint
    • 指定提示性的文本
  • maxLines
    • 指定最大行数
    • 输入的内容超过指定行数后文本会向上滚动,不再继续wrap拉伸
  • getText().toString()
    • 获取文本框中输入的内容

ImageView

<ImageView
    android:id="@+id/image_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/img"
    />
  • src
  • setImageResource()

ProgressBar

<ProgressBar
    android:id="@+id/progress_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    style="?android:attr/progressBarStyleHorizontal"
    android:max="100"
    />
  • style
    • 指定样式
    • ?android:attr/progressBarStyleHorizontal:水平样式
  • max
    • 设置最大值,然后在代码中动态地更改进度条的进度
  • visibility
    • visible:可见
    • invisible:不可见但仍然占据原来的位置和大小
    • gone:不可见且不占用哦那个原来的位置和大小
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.button:
            if (progressBar.getVisibility() == View.GONE){
                progressBar.setVisibility(View.VISIBLE);
            } else {
                progressBar.setVisibility(View.GONE);
            }
            
            int progress = progressBar.getProgress();
            progress = progress + 10;
            progressBar.setProgress(progress);
            break;
        default:
            break;
    }
}

AlertDialog

  • 在当前的界面弹出一个对话框,置顶于所有界面元素之上,能够屏蔽其他控件的交互能力
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.button:
            AlertDialog.Builder dialog = new AlertDialog.Builder(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():取消按钮的点击事件

ProgressDialog

  • 弹出一个对话框,屏蔽掉其他控件的交互能力,显示进度条
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.button:
            ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
            dialog.setTitle("This is ProgressDialog");
            dialog.setMessage("Loading...");
            dialog.setCancelable(true);
            dialog.show();
            break;
        default:
            break;
    }
}
  • setCancelable()
    • false:不能通过Back键取消,加载完必须调用ProgressDialog的dismiss()来关闭对话框

4种基本布局

线性布局 LinearLayout

  • 包含的所有空间在线性方向上依次排列
  • orientation:指定排列方向
    • vertical/default:horizontal
    • 如果排列方向是horizontal,控件宽度就不能指定为match_parent;反之亦然。
  • layout_gravity
    • 指定控件在布局中的对齐方式
    • 如果排列方向是horizontal,只有垂直方向上的对齐方式才会生效;反之亦然。
  • layout_weight
    • 使用比例的方式指定控件的大小
    • 可用于手机屏幕适配性
    • 把LinearLayout下的所有控件的layout_width值相加得到总值
      每个控件所占大小的比例就是该控件的layout_weight值除以总值
    • wrap_content的控件仍按值计算,match_parent的控件占屏幕剩余的空间

相对布局 RelativeLayout

  • 通过相对定位的方式让控件出现在布局的任何位置
  • 控件相对于控件布局 E.g. android:layout_above = @id/button
  • layout_above layout_below layout_toLeftOf layout_toRightOf
    layout_alignTop layout_alignBottom layout_alignLeft layout_alignRight

帧布局 FrameLayout

  • 所有控件都会默认摆放在布局的左上角
  • 可以用layout_gravity指定控件在布局中的对齐方式
  • 应用较少

百分比布局 PercentFrameLayout/PercentRelativeLayout

  • 允许直接指定控件在布局总所占的百分比
  • 定义在support库中,在项目的build.gradle中添加依赖,就能保证百分比布局在安卓所有系统版本上的兼容性
  • <android.support.percent.PercentFrameLayout
        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">
        
    </android.support.percent.PercentFrameLayout>    
    
  • app:layout_widthPercent
    app:layout_heightPercent

AbsoluteLayout TableLayout etc.

自定义控件

  • 新建布局title.xml(带自定义的标题栏的布局)

引入布局

<include layout="@layout/title"

将系统自带的标题栏隐藏掉

ActionBar actionbar = getSupportActionBar();
if (actionbar != null) {
    actionbar.hide();
}

创建自定义控件

public class TitleLayout extends LinearLayout {
    public TitleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title, this);
    }
}
  • 在布局文件中添加自定义控件
    <com.example.uicustomviews.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    

ListView

  • 允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内
    同时屏幕上原有的数据会滚动出屏幕
  • 最常用&最难用

简单用法

  • 在布局中加入ListView
    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    
  • 在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,借助适配器传递
    • 在ArrayAdapter的构造函数中一次传入当前上下文、ListView的子项布局的id,以及要适配的数据
    • android.R.layout.simple_list_item_1:Android内置的布局文件,里面只有一个TextView
    • setAdapter():将构建好的适配器对象传递进去

传递类给ListView

  • 新建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;
        }
    }
    
  • 为ListView的子项指定自定义布局 fruit_item.xml
    <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_gravity="center_vertical"
            android:layout_marginLeft="10dp" />
    
    </LinearLayout>
    
  • 创建自定义适配器
    public class FruitAdapter extends ArrayAdapter<Fruit> {
        private int resourceId;
    
        public FruitAdapter(Context context, int textViewResourceId,
                            List<Fruit> objects) {
            super(context, textViewResourceId, objects);
            resourceId = textViewResourceId;
        }
    
        @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;
        }
    }
    
    • LayoutInflater()的第三个参数
      false:只让在父布局中声明layout属性生效,但不为这个View添加父布局
      ListView的标准写法

提升ListView的运行效率

  • FruitAdapter中getView()每次都将布局重新加载了一遍
    getView()中有一个convertView参数,用于将之前加载好的布局进行缓存,以便之后重用
    View view;
    if (convertView == null) {
        view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
    } else {
        view = convertView;
    }
    

etc.

ListView的点击事件

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) {
        Fruit fruit = fruitList.get(position);
        Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
    }
});
  • setOnItemClickListener为ListView注册一个监听器
    当用户点击ListView中的任意子项时,回调onItemClick()方法
    该方法通过position判断出用户点击的是哪个子项

RecyclerView

  • 定义在support库中,在项目的build.gradle中添加依赖库
  • 在dependencies闭包中添加:
    compile 'com.android.support:recyclerview-v7:24.2.1'

基本用法

  • 适配器
    public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
        private List<Fruit> mFruitList;
    
        static class ViewHolder extends  RecyclerView.ViewHolder {
            ImageView fruitImage;
            TextView fruitName;
    
            public ViewHolder(View view) {
                super(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);
            ViewHolder holder = new ViewHolder(view);
            return holder;
        }
    
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            Fruit fruit = mFruitList.get(position);
            holder.fruitImage.setImageResource(fruit.getImageId()); 
            holder.fruitName.setText(fruit.getName());
        }
    
        @Override
        public int getItemCount() {
            return mFruitList.size();
        }
    
    }
    
    • ViewHolder继承自RecyclerView.ViewHolder
      ViewHolder的构造函数中要传入一个View参数,通常为RecyclerView子项的最外层布局
    • onCreateViewHolder()用于创建ViewHolder实例
    • onBindViewHolder用于对RecyclerView子项的数据进行赋值
  • LayoutManager用于指定RecyclerView的布局方式
    LinearLayoutManager线性布局
    GridLayoutManager网格布局
    StaggeredGridLayoutManager瀑布流布局

横向滚动和瀑布流布局

  • 横向滚动

    • 把fruit_item.xml里的元素改成垂直排列
      android:orientation="vertical"
      android:layout_gravity="certer_horizontal" ListView的布局排列是由自身管理的, RecyclerView由LayoutManager管理
      LayoutManager中制定了一套可扩展的布局排列接口
  • 瀑布流布局

    recyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    StaggeredGridLayoutManager layoutManager = new 
            StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
    recyclerView.setLayoutManager(layoutManager);
    

getRandomLengthName()使用Random对象创造一个1-20的随机数,然后将参数中传入的字符串重复随机遍

RecyclerView的点击事件

  • onCreateViewHolder()中注册点击事件
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
    final ViewHolder holder = new ViewHolder(view);
    holder.fruitView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int position = holder.getAdapterPosition();
            Fruit fruit = mFruitList.get(position);
            Toast.makeText(v.getContext(), "you clicked view" + fruit.getName(),
                Toast.LENGTH_SHORT).show();
        }
    });
    holder.fruitImage.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int position = holder.getAdapterPosition();
            Fruit fruit = mFruitList.get(position);
            Toast.makeText(v.getContext(), "you clicked image" + fruit.getName(),
                    Toast.LENGTH_SHORT).show();
        }
    });
    
    • 分别为外层布局和ImageView注册了点击事件

其他

制作Nine-Patch图片(部分拉伸)

  • 打开draw9patch.bat文件,并把图片加载进来
  • 在图片的四个边框绘制小黑点,拉伸时只拉伸黑点标记的区域
  • 鼠标在边缘拖动绘制,按住Shift键拖动擦除