第"三"行代码-UI

4 UI开发

01 该如何编写程序界面

  Android应用程序的界面主要是通过编写XML的方式来实现的。写XML的好处是,我们不仅能够了解界面背后的实现原理,而且编写出来的界面还可以具备很好的屏幕适配性。

  Google又推出了一个全新的界面布局:ConstraintLayout。和以往传统的布局不同,ConstraintLayout不是非常适合通过编写XML的方式来开发界面,而是更加适合在可视化编辑器中使用拖放控件的方式来进行操作,并Android Studio中也提供了非常完备的可视化编辑器。

02 常用控件的使用方法

1 TextView

  用法

<TextView
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textColor="#00ff00"
    android:textSize="24sp"
    android:text="This is TextView"/>

image-20210903101525364

2 Button

  用法

button.setOnClickListener{
    
}
class MainActivity : AppCompatActivity() ,View.OnClickListener{
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main4)
        
    }

    override fun onClick(v: View?) {
        when(v?.id){
            R.id.button -> println("button is clicked")
        }
    }
}
3 EditText

  用法

<EditText
    android:id="@+id/editText"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Type something here"
    android:maxLines="2"
    />

  当输入的内容超过两行时,文本就会向上滚动,EditText则不会再继续拉伸

4 ImageView

  主流分辨率:xxhdpi. 新建一个drawable-xxhdpi目录

  设置数据源

imageView.setImageResource(R.drawable.img_2)
5 ProgressBar

  用法

//进度条消失
when (v?.id) {
    R.id.button -> {
        if (progressBar.visibility == View.VISIBLE) {
            progressBar.visibility = View.GONE
        } else {
            progressBar.visibility = View.VISIBLE
        }
    }
}

image-20210903103346369

  • visible表示控件是可见的,这个值是默认值,不指定android:visibility时,控件都是可见的。

  • invisible表示控件不可见,但是它仍然占据着原来的位置和大小,可以理解成控件变成透明状态了。

  • gone则表示控件不仅不可见,而且不再占用任何屏幕空间。

  水平进度条

<ProgressBar
    android:id="@+id/progressBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    style="?android:attr/progressBarStyleHorizontal"
    android:max="100"
    />

  动态设置进度

when (v?.id) {
    R.id.button -> progressBar.progress = progressBar.progress + 10

}

image-20210903103953175

6 AlertDialog

  用法

R.id.button -> {
    AlertDialog.Builder(this).apply {
        setTitle("This is Dialog")
        setMessage("Something important.")
        setCancelable(false)
        setPositiveButton("OK") { dialog, which ->
            Toast.makeText(context,"OK",Toast.LENGTH_SHORT).show()
        }
        setNegativeButton("Cancel") { dialog, which ->
        }
        show()
    }
}
//java
AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setIcon(R.mipmap.ic_launcher)
                .setTitle("这是一个对话框")
                .setMessage("这是消息")
                .setView(dialogView)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Log.e(TAG, "onClick: 确定" );
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Overrid面实现,图4.15很好地展示了它们之间的关系。e
                    public void onClick(DialogInterface dialog, int which) {
                        Log.e(TAG, "onClick: 取消" );
                    }
                })
                .setNeutralButton("中间", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Log.e(TAG, "onClick: 中间" );
                    }
                })
                .create()
                .show();

image-20210903111441914

03 详解3种布局

  布局是一种可用于放置很多控件的容器,它可以按照一定的规律调整内部控件的位置,从而编写出精美的界面。

image-20210906094328943

1 LinearLayout

  LinearLayout又称作线性布局,是一种非常常用的布局。

  当然,如果不指定android:orientation属性的值,默认的排列方向就是horizontal。

  android:gravity用于指定文字在控件中的对齐方式,而android:layout_gravity用于指定控件在布局中的对齐方式。android:layout_gravity的可选值和android:gravity差不多,但是需要注意,当LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效。当LinearLayout的排列方向是vertical时,只有
水平方向上的对齐方式才会生效。

指定layout_weight的效果

2 RelativeLayout

  RelativeLayout又称作相对布局,可以通过相对定位的方式让控件出现在布局的任何位置。

相对父类容器

android:layout_alignParentLeft

android:layout_alignParentTop

android:layout_alignParentRight

android:layout_alignParentBottom

android:layout_centerInParent

相对空间布局

  • android:layout_above属性可以让一个控件位于另一个控件的上方,需要为这个属性指定相对控件id的引用,这里我们填入了@id/button3,表示让该控件位于Button 3的上方。其他的属性也是相似的

  • android:layout_below表示让一个控件位于另一个控件的下方

  • android:layout_toLeftOf表示让一个控件位于另一个控件的左侧

  • android:layout_toRightOf表示让一个控件位于另一个控件的右侧。

另外

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

android:layout_alignRight表示让一个控件的右边缘和另一个控件的右边缘对齐。其他类似。

3 FrameLayout

  FrameLayout又称作帧布局,它相比于前面两种布局就简单太多了,因此它的应用场景少了很多。这种布局没有丰富的定位方式,所有的控件都会默认摆放在布局的左上角

04 自定义控件

常用控件和布局的继承结构

  可以看到,我们所用的所有控件都是直接或间接继承自View的,所用的所有布局都是直接或间接继承自ViewGroup的。View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View的基础上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器

1 引入布局

使用include引入布局

<include layout="@layout/title" />
2 自定义控件

title.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >
    <Button
        android:id="@+id/titleBack"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="#ff00ff"
        android:text="Back"
        android:textColor="#fff" />
    <TextView
        android:id="@+id/titleText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Title Text"
        android:textColor="#fff"
        android:textSize="24sp" />
    <Button
        android:id="@+id/titleEdit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="#ff00ff"
        android:text="Edit"
        android:textColor="#fff" />
</LinearLayout>

新建titleLayout继承LinearLayout

class TitleLayout(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
    init {
        LayoutInflater.from(context).inflate(R.layout.activity_main4, this)
        titleBack.setOnClickListener {
            val activity = context as Activity
            activity.finish()
        }
        titleEdit.setOnClickListener {
            Toast.makeText(context, "You clicked Edit button", Toast.LENGTH_SHORT).show()
        }
    }
}

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

  之后我们可以使用新定义的控件了。

05 最常用、难用控件ListView

1 简单用法
private val data = listOf("Apple", "Banana", "Orange", "Watermelon",
    "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",
    "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape",
    "Pineapple", "Strawberry", "Cherry", "Mango")



override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main4)

    val adapter = ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data)
    listView.adapter = adapter
}
2 定制页面 fruit_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"/>
    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp" />
</LinearLayout>

水果类

class Fruit(val name:String,val imageId:Int) {
}

Adapter

class FruitAdapter(activity: Activity, val resourceId: Int, data: List<Fruit>) :
    ArrayAdapter<Fruit>(activity, resourceId, data) {

    inner class ViewHolder(val fruitImage:ImageView,val fruitName:TextView)

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val view :View

        //viewhHolder避免重复find布局中的view
        val viewHolder:ViewHolder

        //convertView进行缓存,提高运行效率,避免重复加载布局
        if (convertView == null) {
            view = LayoutInflater.from(context).inflate(resourceId, parent, false)
            val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
            val fruitName:TextView = view.findViewById(R.id.fruitName)
            viewHolder = ViewHolder(fruitImage, fruitName)
            view.tag = viewHolder
        } else {
            view = convertView
            viewHolder = view.tag as ViewHolder
        }


        val fruit = getItem(position) //获取当前项的fruit实例
        if (fruit!=null){
            viewHolder.fruitImage.setImageResource(fruit.imageId)
            viewHolder.fruitName.text = fruit.name;
        }
        return view
    }
}

在activity中使用

class MainActivity4 : AppCompatActivity() {

    private val data = listOf("Apple", "Banana", "Orange", "Watermelon",
        "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",
        "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape",
        "Pineapple", "Strawberry", "Cherry", "Mango")

    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main4)

        initFruits()
        val adapter = FruitAdapter(this,R.layout.fruit_item,fruitList)
        //1
        listView.adapter = adapter

        //2.点击事件
        listView.setOnItemClickListener { _, _, position, _ ->
            val fruit = fruitList[position]
            Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
        }
    }
    private fun initFruits() {
        repeat(2) {
            fruitList.add(Fruit("Apple", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Banana",R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Orange", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Watermelon", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Pear", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Grape", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Pineapple", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Strawberry", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Cherry", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Mango", R.drawable.ic_launcher_background))
        }
    }
}

06 RecyclerView

1 基本使用

添加依赖

implementation 'androidx.recyclerview:recyclerview:1.0.0'

  当你不能确定最新的版本号是多少的时候,可以就像上述代码一样填入1.0.0,当有更新的库版本时,Android Studio会主动提醒你,并告诉你最新的版本号是多少.

水果类fruit

adapter

class FruitAdapter(val fruitList:List<Fruit>): RecyclerView.Adapter<FruitAdapter.ViewHolder>() {

    inner class ViewHolder(view:View):RecyclerView.ViewHolder(view){
        val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
        val fruitName:TextView = view.findViewById(R.id.fruitName)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val fruit = fruitList[position]
        holder.fruitImage.setImageResource(fruit.imageId);
        holder.fruitName.text = fruit.name
    }
    override fun getItemCount() = fruitList.size
}

  这是RecyclerView适配器标准的写法,虽然看上去好像多了好几个方法,但其实它比ListView的适配器要更容易理解。这里我们首先定义了一个内部类ViewHolder,它要继承自RecyclerView.ViewHolder。然后ViewHolder的主构造函数中要传入一个View参数,这个参数通常就是RecyclerView子项的最外层布局,那么我们就可以通过findViewById()方法来获取布局中ImageView和TextView的实例了。

  继续往下看,由于FruitAdapter是继承自RecyclerView.Adapter的,那么就必须重写onCreateViewHolder()、onBindViewHolder()和getItemCount()这3个方法。

  • onCreateViewHolder()方法是用于创建ViewHolder实例的,我们在这个方法中将fruit_item布局加载进来,然后创建一个ViewHolder实例,并把加载出来的布局传入构造函数当中,最后将ViewHolder的实例返回。

  • onBindViewHolder()方法用于对RecyclerView子项的数据进行赋值,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。

  • getItemCount()方法就非常简单了,它用于告诉RecyclerView一共有多少子项,直接返回数据源的长度就可以了。

在activity中使用

class MainActivity4 : AppCompatActivity() {

    private val data = listOf("Apple", "Banana", "Orange", "Watermelon",
        "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",
        "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape",
        "Pineapple", "Strawberry", "Cherry", "Mango")

    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main4)

        initFruits()
        val layoutManager = LinearLayoutManager(this)
        layoutManager.orientation = LinearLayoutManager.HORIZONTAL
        recyclerView.layoutManager = layoutManager

        val adapter = FruitAdapter(fruitList)
        recyclerView.adapter = adapter
    }
    private fun initFruits() {
        repeat(2) {
            fruitList.add(Fruit("Apple", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Banana",R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Orange", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Watermelon", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Pear", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Grape", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Pineapple", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Strawberry", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Cherry", R.drawable.ic_launcher_background))
            fruitList.add(Fruit("Mango", R.drawable.ic_launcher_background))
        }
    }
}

  LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。接下来我们创建了FruitAdapter的实例,并将水果数据传入FruitAdapter的构造函数中,最后调用RecyclerView的setAdapter()方法来完成适配器设置,这样RecyclerView和数据之间的关联就建立完成了。

image-20211008112945871

2 实现横向滚动和瀑布流

  横向滚动,设置recyclerView的layoutmanager即可

val layoutManager = LinearLayoutManager(this)
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
recyclerView.layoutManager = layoutManager

  瀑布流,设置recyclerView的layoutmanager即可。为了让瀑布流更直观,text的长度使用getRandomLengthString变成随机长度。

val layoutManager = StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL) //3是一行有几个
recyclerView.layoutManager = layoutManager
private fun initFruits() {
    repeat(2) {
        fruitList.add(Fruit(getRandomLengthString("Apple"), R.drawable.ic_launcher_background))
        fruitList.add(Fruit(getRandomLengthString("Banana"),R.drawable.ic_launcher_background))
        fruitList.add(Fruit(getRandomLengthString("Orange"), R.drawable.ic_launcher_background))
        fruitList.add(Fruit(getRandomLengthString("Watermelon"), R.drawable.ic_launcher_background))
        fruitList.add(Fruit(getRandomLengthString("Pear"), R.drawable.ic_launcher_background))
        fruitList.add(Fruit(getRandomLengthString("Grape"), R.drawable.ic_launcher_background))
        fruitList.add(Fruit(getRandomLengthString("Pineapple"), R.drawable.ic_launcher_background))
        fruitList.add(Fruit(getRandomLengthString("Strawberry"), R.drawable.ic_launcher_background))
        fruitList.add(Fruit(getRandomLengthString("Cherry"), R.drawable.ic_launcher_background))
        fruitList.add(Fruit(getRandomLengthString("Mango"), R.drawable.ic_launcher_background))
    }
}
private fun getRandomLengthString(str: String): String {
    val n = (1..20).random()
    val builder = StringBuilder()
    repeat(n) {
        builder.append(str)
    }
    return builder.toString()
}

image-20211008175214717

3 点击事件

  设置adapter的onCreateViewHolder即可设置点击事件。

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)
    val viewHolder = ViewHolder(view)
    viewHolder.itemView.setOnClickListener {
        val position = viewHolder.adapterPosition
        val fruit = fruitList[position]
        Toast.makeText(parent.context, "you clicked view ${fruit.name}",
            Toast.LENGTH_SHORT).show()
    }
    viewHolder.fruitImage.setOnClickListener{
        val position = viewHolder.adapterPosition
        val fruit = fruitList[position]
        Toast.makeText(parent.context, "you clicked image ${fruit.name}",
            Toast.LENGTH_SHORT).show()
    }
    return viewHolder
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值