安卓基础巩固(一):工程结构、基本概念、常用布局、基本组件、动画

news2024/11/15 7:02:57

文章目录

  • 安卓项目结构
    • AndroidMainfest.xml
    • res资源目录简介
  • 基本概念
    • Layout
      • R类
    • Application与Activity
    • Context
    • Intent
      • 数据传递
        • 可传递的数据类型
        • intent.putExtra()和使用Bundle的区别
        • 数据传递大小的限制
      • 通过Intent 过滤器接收隐式 Intent:
    • 单位和尺寸
      • px与pt的区别
      • dp与sp
  • 布局
    • LinearLayout
    • RelativeLayout
    • TableLayout
    • FrameLayout
    • ConstraintLayout
    • ListView
      • 基于ArrayAdapter
      • 自定义Adaper
      • 提升ListView的运行效率
    • RecyclerView
      • 基本属性
      • 使用案例
      • 布局(显示方式)
      • 监听事件
        • 利用View.onClickListener 和 onLongClickListener
    • ViewPager
    • ViewPager2
      • 什么是懒加载?
      • ViewPager与ViewPager2部分对比
      • 常见api
  • 动画
    • 帧动画
    • 补间动画
    • 属性动画

安卓项目结构

在这里插入图片描述

app目录下包含三个主要的目录:

  • manifest:默认生成一个AndroidMainfest.xml 文件,也称清单文件,在里边配置APP的权限,启动组件等基本配置信息。
  • java:该文件下存放Activity的实现文件
  • res:资源目录,存放图片、图标、颜色、字体等界面渲染所需要的资源。

在APP目录下有一个build.gradle文件,俗称“模块的gradle”文件或者APP的gradle文件,这个文件中,包含有Android编译的版本号信息、APP的版本信息,JDK版本号、项目的依赖库等。

引入外部的依赖或者外部的SDK也在这个文件中添加。
在这里插入图片描述

在项目的根目录下还有一个gradle文件,这里指定了仓库名,全局依赖,项目根目录名等信息,该文件一般不需要更改。
在这里插入图片描述

AndroidMainfest.xml

也可以简称为「manifest文件」。清单文件非常重要,它告诉系统我们的app有哪些activity,用到了什么权限等等信息。
注意:所有的四大组件都需要在AndroidMainfest.xml 中进行注册!

res资源目录简介

简单介绍Android工程中的资源目录(resources),res。

资源是指代码使用的附加文件和静态内容,例如位图、布局定义、界面字符串、动画说明等。
在这里插入图片描述

把资源放进对应的目录后,可使用在项目R类中生成的资源ID来访问这些资源。形如R.drawable.icon,R.layout.main_activity。 R类是自动生成的。代表resources。

资源文件只能以小写字母和下划线做首字母,随后的名字中只能出现【a-z0-9_.】这些字符,否则R.java不会自动更新!!!

将各类资源放入项目 res/ 目录的特定子目录中。 子目录的名字特别重要。我们把不同的资源放到不同的子目录中(res子目录)。参考下面的表格。
在这里插入图片描述

注意:

  1. 不能将资源文件直接保存在res/目录内,因为会造成编译错误
  2. mipmap 目录是专门用于存放应用程序的启动图标(App Icon)资源的。mipmap 目录下的图标资源会自动进行缩放适应不同分辨率设备,而 drawable 目录下的图像资源需要手动提供适应不同屏幕密度的图像资源。

基本概念

Layout

  • Layout:布局文件,相当于前端的HTML文件。

  • LayoutParams相当于一个Layout的信息包,它封装了Layout的位置,高、宽等信息。

R类

  • 当Android应用程序被编译,会自动生成一个R.java类,其中包含了所有的res/目录下资源的ID,如布局文件,资源文件,图片。 res目录下保存的文件大多数都会被编译,并且被赋予资源ID,这些ID被保存在R.java文件中,这样我们就可以在程序中通过ID来访问res类的资源。

  • 资源文件只能以小写字母和下划线做首字母,随后的名字中只能出现【a-z0-9_.】这些字符,否则R.java不会自动更新!!!

Application与Activity

  • 应用程序每次启动时,系统会为其创建一个application对象且只有一个(单例类),用来存储系统的一些信息,相当于一个容器。
    启动application时,系统会创建一个PID(进程ID),所有的activity都在这个进程上运行,在application创建时会初始化一个全局变量,同一个应用的activity,都可以获取到这个变量,也就是说,某一个activity中改变了这个变量,其他activity里也会改变。 application是对应用程序的抽象。

  • Activity表示屏幕中的一个活动,用于显示屏幕界面并与用户进行交互,Activity是对可交互UI界面的抽象。

Context

Context 在 Android 开发中几乎无处不在,它是 Android 开发中最重要的东西,所以我们必须了解正确使用它。

==错误使用 Context 很容易导致 android 应用程序中的内存泄漏。==比如在单例的场景下传递activity的context。

Context:直译为语境或上下文,它有以下几个特点:

  • 应用程序当前状态的上下文
  • 用于获取有关活动和应用程序的信息
  • 用于访问资源、数据库和共享首选项
  • Activity和Application类都扩展了Context类

在Android中主要有两种Context:

  1. Application Context:它是应用程序中扩展的Context,与Application 生命周期相关,伴随Application的一生,只被创建和销毁一次。
  2. Activity context:存在于活动中的context,伴随活动的生命周期被创建和销毁。

什么时候用哪个Context?

  • 永远记住,在单例的情况下(生命周期附加到应用程序生命周期),总是使用Application Context。
  • 始终尝试使用最近的Context,当在Activity中,对于任何UI操作(例如显示 toast、对话框等),都需要使用 Activity 上下文。

Intent

Intent ,翻译为意图,是一个消息传递对象,您可以用来从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个:

  • 启动 Activity
  • 启动服务
  • 传递广播

Intent 对象携带 Android 系统用来确定要启动哪个组件的信息(例如,准确的组件名称或应当接收该 Intent 的组件类别),以及收件人组件为了正确执行操作而使用的信息(例如,要采取的操作以及要处理的数据)。

Intent 中包含的主要信息如下:

  • 组件名称:要启动的组件名称。这是一个可选项,如果不指定组件名称,则为隐式Intent,系统将根据其他 Intent 信息(例如,以下所述的操作、数据和类别)决定哪个组件应当接收 Intent。如需在应用中启动特定的组件,则应指定该组件的名称。

  • 操作类型:您可以指定自己的操作,供 Intent 在您的应用内使用(或者供其他应用在您的应用中调用组件)。但是,您通常应该使用由Intent 类或其他框架类定义的操作常量。以下是一些用于启动 Activity 的常见操作:

    • ACTION_VIEW
      如果您拥有一些某项 Activity 可向用户显示的信息(例如,要使用图库应用查看的照片;或者要使用地图应用查看的地址),请通过 Intent 将此操作与 startActivity() 结合使用。
    • ACTION_SEND
      这也称为共享 Intent。如果您拥有一些用户可通过其他应用(例如,电子邮件应用或社交共享应用)共享的数据,则应使用 Intent 将此操作与 startActivity() 结合使用。
  • Extra:携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 URI 一样,有些操作也使用特定的 extra。
    您可以使用各种 putExtra() 方法添加 extra 数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有 extra 数据的 Bundle 对象,然后使用 putExtras() 将 Bundle 插入 Intent 中。

数据传递

可传递的数据类型

  • 8 种基本数据类型(boolean、 byte、 char、 short、 int、 long、 float、 double)、String
  • Intent、Bundle
  • Serializable对象、Parcelable及其对应数组、CharSequence 类型
  • ArrayList,泛型参数类型为:、<? Extends Parcelable>

intent.putExtra()和使用Bundle的区别

Bundle:直译为捆绑,相当于一个数据容器,更多适用于:

  • 连续传递数据:Activity A -> B -> C,使用putExtra(),则需写两次 intent = A->B 先写一遍 + 在B中取出来 & 再把值重新写到Intent中再跳到C;若使用 Bundle,则只需取出 & 传入 Bundle对象即可
  • putExtra无法无法传递对象,而 Bundle 则可通过 putSerializable 传递对象

数据传递大小的限制

Intent 传递大数据,会出现 TransactionTooLargeException 的场景,这是因为 Intent 传递数据的大小是有限制的。

Android 系统使用一种称为 Binder 机制的进程间通信机制来传递 Intent。在此过程中,Intent 的数据被封装成一个 Parcel 对象,并通过 Binder 传递给目标组件。然而,Binder 机制对于单个事务(Transaction)的数据大小有一个限制,通常为 1MB,并且这里的 1MB 空间并不是当前操作独享的,而是由当前进程所共享。

为了避免出现 TransactionTooLargeException 异常,可以考虑以下解决方案:

  • 使用其他数据传递机制:对于大数据传递,可以使用其他机制,如共享文件、ContentProvider 或者使用数据库等。

  • 传递数据的引用:而不是将整个数据对象传递给目标组件,可以传递数据的引用或标识符,目标组件在需要时再获取数据。

  • 分割数据:将大的数据拆分成较小的块进行传递,通过多个 Intent 或其他方式进行传递,然后在目标组件中重新组装。

  • 使用 Parcelable 替代 Serializable:Parcelable 是一种 Android 提供的更高效的序列化机制,相对于 Serializable,它可以减少数据的大小。

通过Intent 过滤器接收隐式 Intent:

要公布应用可以接收哪些隐式 Intent,请在清单文件中使用 <intent-filter> 元素为每个应用组件声明一个或多个 Intent 过滤器。每个 Intent 过滤器均根据 Intent 的操作、数据和类别指定自身接受的 Intent 类型。仅当隐式 Intent 可以通过 Intent 过滤器之一传递时,系统才会将该 Intent 传递给应用组件。

<activity android:name="MainActivity">
    <!-- This activity is the main entry, should appear in app launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity">
    <!-- This activity handles "SEND" actions with text data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

单位和尺寸

像素:是指在由一个数字序列表示的图像中的一个最小单位,称为像素。

px与pt的区别

  • px:pixels(像素),不同设备显示效果相同
  • pt:point,是一个标准的长度单位,1pt = 1/72英寸,用于印刷业,简单易用。

dp与sp

  • dip:device independent pixels(设备独立像素)。不同设备有不同的显示效果,这与设备的硬件有关,一般我们为了支持WVGA、HVGA和QVGA推荐使用这个,不依赖像素。
  • dp:就是dip
  • sp:scaled pixels(放大像素),用于字体显示(best for textsize)

因为我们的APP可能需要在不同的设备上使用,使用dp可以根据设备不同,自适应大小(设备越大,1dp所占用的像素越多)。因此组件的大小常常使用dp为单位。

布局

安卓常用的布局方式有六种(绝对布局因灵活性太差已经弃用):

  • 线性布局
  • 相对布局
  • 网格布局
  • 表格布局
  • 帧布局
  • 约束布局
    在这里插入图片描述

LinearLayout

LinearLayout里面可以放置多个view(这里称为子view,子项)。 子view可以是TextView,Button,或者是LinearLayout,RelativeLayout等等。 它们将会按顺序依次排布为一列或一行。

常用属性

  • orientation:确定水平或竖直排布子view。 可选值有vertical和horizontal。
  • gravity:决定子view的排布方式。gravity有“重力的意思”,引申为子view会向哪个方向靠拢,gravity有几个选项可以选择,我们常用的有start,end,left,right,top,bottom。
  • 子view的layout_gravity:gravity是控制自己内部的子元素,layout_gravity是告诉父元素自己的位置。可以设置子view的layout_weight来控制空间占比,设置layout_weight的时候,一般要设置子view的layout_width(水平排布时)或者layout_height(垂直排布时)为0。
  • divider:设置divider和showDivider属性,使得子view之间有分割线。

RelativeLayout

RelativeLayout和LinearLayout类似,都是ViewGroup,能“容纳”多个子view。RelativeLayout 是一个以相对位置显示子视图的视图组。每个视图的位置可以指定为相对于同级元素的位置(例如,在另一个视图的左侧或下方)或相对于父级 RelativeLayout 区域的位置(例如在底部、左侧或中心对齐)((由 ID 确定)的位置)。

TableLayout

表格布局,通过设置表格的行列,构建布局。
常用属性
layout_column:显示在第几列
layout_columnSpan:横向跨几列
layout_columnWeight:剩余空间分配方式
layout_gravity:在网格中的显示位置
layout_row:显示在第几行
layout_rowSpan:横向跨几行
layout_rowWeight:纵向剩余空间分配

FrameLayout

帧布局:特点是子view是可以重叠的。

ConstraintLayout

ConstraintLayout 可让您使用扁平视图层次结构(无嵌套视图组)创建复杂的大型布局。它与RelativeLayout 相似,其中所有的视图均根据同级视图与父布局之间的关系进行布局,但其灵活性要高于 RelativeLayout。

注意:约束布局要求每个视图至少有两个约束条件:一个水平约束,一个垂直约束。如果缺少某个方向的约束,比如垂直,那么默认是贴近上边界。

还可以通过设定引导线(guide line),指定控件相对于基准线的约束布局。

ListView

ListView,列表视图,能够根据列表中选项个数自适应屏幕显示。ListView本身类似于布局容器,它的子View需要另外定义。在APP运行时,每个列表选项是一个子模块,这个子模块有视图,有对应的数据需要填充到视图,每个子模块还需要绑定对应的事件函数。

安卓开发中,使用了适配器设计模式来处理ListView的显示流程。

适配器模式的定义为:将一个类的接口转为客户所期待的 另一种接口,从而使得原本接口不匹配而无法工作在一起的两个类,能够在一起工作。

ListView期待的是一个有视图有数据有交互功能的列表子模块,在程序运行中,我们先把数据和视图一起加工处理为listview期待的类型,再交给listview工作。

基于ArrayAdapter

如果列表中每个选项的内容可以由一个简单的基本数据类型表示,那么可以使用ArrayAdapter来实现。
例如下边的列表,只需要显示蓝牙的地址,那么只需要一个String类型作为数据传入。
构建步骤如下:

  1. 在activity的布局文件中:声明一个ListView。
    在这里插入图片描述
  2. 额外编写一个device_name.xml,它是每个选项的视图,对于我们的需要而言,一个TextView足以。
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="18sp"
    android:padding="5dp"
/>
  1. 在activity中:
    将device_name.xml构建为ArrayAdpter,然后把适配器交给ListView。当需要向列表中增加选项时,直接调用mPairedDevicesArrayAdapter.add()即可。
//初使化设备适配器存储数组
mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
mUnPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);

//设置已配队设备列表
ListView pairedListView = findViewById(R.id.pairedListView);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener( mDeviceClickListener);

// 设置新查找设备列表
ListView newDevicesListView = findViewById(R.id.unPairedListView);
newDevicesListView.setAdapter(mUnPairedDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mDeviceClickListener);

//         得到本地蓝牙句柄
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
//        //添加已配对设备到列表并显示
if (pairedDevices.size() > 0) {
    findViewById(R.id.pairedListView).setVisibility(View.VISIBLE);
    for (BluetoothDevice device : pairedDevices) {
        mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    }
} else {
    String noDevices = "没有找到已配对的设备。" ;
    mPairedDevicesArrayAdapter.add(noDevices);
}

自定义Adaper

只能显示一段文本的listview太单调了,我们现在就来对listview的界面进行定制,让其丰富内容。
在这里插入图片描述
构建步骤如下:

  1. 在activity.xml中声明一个ListView
  2. 构建每个列表选项中的数据类Fruit:
package com.example.listview2;
public class Fruit {
private int imageID;
private String name;
private String price;
     public int getImageID() {
         return imageID;
     }
     public String getName() {
         return name;
     }
     public String getPrice() {
         return price;
     }
     public Fruit(int imageID, String name, String price) {
         this.imageID = imageID;
         this.name = name;
         this.price = price;
     }
}
  1. 构建每个列表选项布局文件fruit_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:orientation="horizontal"
     android:layout_height="wrap_content">
     <ImageView
         android:id="@+id/fruit_image"
         android:src="@drawable/apple"
         android:layout_width="100dp"
         android:layout_height="80dp"/>
     <TextView
         android:id="@+id/fruit_name"
         android:layout_gravity="center_vertical"
         android:textSize="30sp"
         android:textColor="#000000"
         android:text="name"
         android:layout_marginLeft="10dp"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"/>
     <TextView
         android:id="@+id/fruit_price"
         android:layout_gravity="center_vertical"
         android:textColor="#ff0000"
         android:text="price"
         android:textSize="30sp"
         android:layout_marginLeft="10dp"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"/>
</LinearLayout>
  1. 自定义FruitAdpter类继承自ArrayAdpter
    编写构造方法(构造方法需要传递上下文,数据内容),重写getView()函数。
package com.example.listview2;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
//用于将上下文、listview 子项布局的 id 和数据都传递过来
public class FruitAdapter extends ArrayAdapter<Fruit> {
 public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
     super(context, resource, objects);
 }
//每个子项被滚动到屏幕内的时候会被调用
     @NonNull
     @Override
     public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
     Fruit fruit=getItem(position);//得到当前项的 Fruit 实例
     //为每一个子项加载设定的布局
     View view=LayoutInflater.from(getContext()).inflate(R.layout.fruit_item,parent,false);
     //分别获取 image view 和 textview 的实例
     ImageView fruitimage =view.findViewById(R.id.fruit_image);
     TextView fruitname =view.findViewById(R.id.fruit_name);
     TextView fruitprice=view.findViewById(R.id.fruit_price);
     // 设置要显示的图片和文字
     fruitimage.setImageResource(fruit.getImageID());
     fruitname.setText(fruit.getName());
     fruitprice.setText(fruit.getPrice());
     return view;
     }
}
  1. 在activity中,准备数据,构建ListView并设置Adapter。
package com.example.listview2;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
     //第一步:定义对象
     ListView listView;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         //第二步:绑定控件
         listView = (ListView) findViewById(R.id.list_view);
         //第三步:准备数据
         List<Fruit> fruitlist = new ArrayList<>();
         for (int i = 0; i <2 ; i++) {
             Fruit pineapple=new Fruit(R.drawable.pineapple,"菠萝","¥16.9 元/KG");
             fruitlist.add(pineapple);
             Fruit mango = new Fruit(R.drawable.mango, "芒果","¥29.9 元/kg");
             fruitlist.add(mango);
             Fruit pomegranate = new Fruit(R.drawable.pomegranate, "石榴","¥15元/kg");
             fruitlist.add(pomegranate);
             Fruit grape = new Fruit(R.drawable.grape, "葡萄","¥19.9 元/kg");
             fruitlist.add(grape);
             Fruit apple = new Fruit(R.drawable.apple, "苹果","¥20 元/kg");
             fruitlist.add(apple);
             Fruit orange = new Fruit(R.drawable.orange, "橙子","¥18.8 元/kg");
             fruitlist.add(orange);
             Fruit watermelon = new Fruit(R.drawable.watermelon, "西瓜","¥28.8元/kg");
             fruitlist.add(watermelon);
         }
         //第四步:设计每一个列表项的子布局
         //第五步:定义适配器 控件 -桥梁-数据
         FruitAdapter adapter=new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitlist);
         listView.setAdapter(adapter);
   }
}

提升ListView的运行效率

目前我们的ListView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当页面快速滚动的时候,这将成为性能的瓶颈。
在这里插入图片描述
优化方法一:
仅在convertView为null时才创建:
在这里插入图片描述

优化方法二:
新增内部类ViewHolder对控件实例进行缓存。

在这里插入图片描述

public class FruitAdapter extends ArrayAdapter {
    public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
        super(context, resource, objects);
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {
            viewHolder = new ViewHolder();
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.item, parent, false);
            viewHolder.avatar = convertView.findViewById(R.id.avatar);
            viewHolder.name = convertView.findViewById(R.id.fruit_name);
            viewHolder.price = convertView.findViewById(R.id.fruit_price);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        Fruit fruit = (Fruit) getItem(position);
        viewHolder.price.setText(fruit.getPrice().toString());
        viewHolder.name.setText(fruit.getName());
        viewHolder.avatar.setText(fruit.getAvatar());
        return convertView;
    }

    private final class ViewHolder {
        TextView avatar, name, price;
    }

RecyclerView

RecyclerView是ListView的升级版,它更加灵活,使用更加简单。在ListView中我们可以自己实现ViewHolder以及convertView进行优化,但是在RecyclerView中,它直接封装了ViewHolder的回收利用,也就是RecyclerView将ViewHolder标准化,我们不需要面向 view ,而是直接面向 ViewHolder 编写实现我们需要的 Adapter,这样一来,逻辑结构就变得非常清晰。

RecyclerView常常搭配线性布局和网格布局使用。

基本属性

  • itemAnimator:增删动画
  • itemDecoration:分割线

注意:RecyclerView 本身是不提供点击、长按事件的,而隔壁的 ListView 稳稳支持。对此,可能刚接触 RecyclerView 的同学会疯狂吐槽,怎么作为升级版的 RecyclerView 在这一点上还不如旧版呢?

显然不是。

ListView 中对于点击事件的处理,其实是有很大弊端的,它的 setOnItemClickListener() 方法是为子项注册点击事件,这就导致只能识别到这一整个子项,对于子项中的组件比如按钮就束手无策了。为此,RecyclerView 直接放弃了这个为子项注册点击事件的监听方法,所有点击事件都有具体 View 去注册,好处显而易见,我可以按需为组件注册点击事件,不存在点击不到的组件。

RecyclerView 的核心使用流程如下:

mRecyclerView = findView(R.id.id_recycler_view);
//设置布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
//设置adapter
mRecyclerView.setAdapter(mAdapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration(
                getActivity(), DividerItemDecoration.HORIZONTAL_LIST));

使用案例

下面就来介绍一下 如何通过 RecyclerView 轻松实现一个普通列表:
MainActivity.java:

public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private MyAdapter mMyAdapter;
    private LinearLayoutManager mLayoutManager;
    private List<String> list;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();

        mRecyclerView = findViewById(R.id.recycler_view);
        mMyAdapter = new MyAdapter(list);
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setAdapter(mMyAdapter);
    }

    private void initData() {
        list = new ArrayList<>();
        for (int i = 0; i <= 20; i++) {
            list.add("Item " + i);
        }
    }
}

MyAdapter.java:

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

    //数据源
    private List<String> mList;

    public MyAdapter(List<String> list) {
        mList = list;
    }

    //返回item个数
    @Override
    public int getItemCount() {
        return mList.size() ;
    }

    //创建ViewHolder
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new NormalHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false));
    }

    //填充视图
    @Override
    public void onBindViewHolder(@NonNull final MyAdapter.ViewHolder holder, final int position) {
        holder.mView.setText(mList.get(position));
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mView;

        public ViewHolder(View itemView) {
            super(itemView);
            mView = itemView.findViewById(R.id.text_view);
        }
    }
}

布局(显示方式)

listView默认是垂直布局,而recyclerView则更加灵活,运行我们自己设置它的布局。

可通过LayoutManager(LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager )设置线性布局、网格布局、瀑布流布局;

监听事件

RecycylerView 并没有处理点击事件的监听器,所以如果要监听 RecycylerView 的点击事件,我们需要自己写监听器。
下面就简单介绍几种实现方法。
推荐使用方法一和方法三

  • 方法一:利用View.onClickListener 和 onLongClickListener
  • 方法二:利用RecyclerView.OnItemTouchListener
  • 方法三:利用GestureDetector(手势检测类)对方法二优化

利用View.onClickListener 和 onLongClickListener

  1. 在adapter中新建两个内部接口:
public interface OnItemClickListener {
    void onItemClick(View view, int position);
}
 
public interface OnItemLongClickListener {
    void onItemLongClick(View view, int position);
}
  1. 新建两个私有变量用于保存用户设置的监听器,并公开一个设置监听器的方法:
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
 
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
    this.mOnItemClickListener = mOnItemClickListener;
}
 
public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
    this.mOnItemLongClickListener = mOnItemLongClickListener;
}
  1. 在onBindViewHolder方法内,实现回调:
    @Override
    public void onBindViewHolder(final MyViewHolder holder, int position) {
        holder.tvTest.setText(stringList.get(position));
//        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
//        lp.height = (int) (100 + Math.random() * 300);
//        holder.itemView.setLayoutParams(lp);
        //判断是否设置了监听器
        if(mOnItemClickListener != null){
            //为ItemView设置监听器
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = holder.getLayoutPosition(); // 1
                    mOnItemClickListener.onItemClick(holder.itemView,position); // 2
                }
            });
        }
        if(mOnItemLongClickListener != null){
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int position = holder.getLayoutPosition();
                    mOnItemLongClickListener.onItemLongClick(holder.itemView,position);
                    //返回true 表示消耗了事件 事件不会继续传递
                    return true;
                }
            });
        }
    }

这里实际上用到了子 Item View 的 onClickListener 和onLongClickListener这两个监听器,如果当前子item view被点击了,会触发点击事件进行回调,然后在内部接口处获取当前点击位置的position值,接着在我们保存的用户设置的监听器处进行再次回调,而这一次的回调是我们自己手动添加的,需要实现上面所述的接口。
修改完 TestAdapter后,我们接着在 MainActivity 中设置监听器,采用匿名内部类的形式实现了 onItemClickListener 、 onItemLongClickListener 接口,这种写法与一般的设置监听器的流程相同:

TestAdapter mTestAdapter = new TestAdapter(getList());
mTestAdapter.setOnItemClickListener(new TestAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MainActivity.this, "click " + getList().get(position), Toast.LENGTH_SHORT).show();
    }
});
mTestAdapter.setOnItemLongClickListener(new TestAdapter.OnItemLongClickListener() {
    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(MainActivity.this,"long click "+getList().get(position),Toast.LENGTH_SHORT).show();
    }
});
rvTest.setAdapter(mTestAdapter);

ViewPager

一个简单的页面切换组件。
使用案例:

编写三个页面进行切换。

  1. 首先创建3个xml布局文件
  2. 在activity的布局文件中声明ViewPager
  3. 创建Adapter继承PagerAdapter,重写方法:
    • getCount():获取viewpager中有多少个view
    • instantiateItem():
      • 将给定位置的view添加到viewgroup中,创建并显示处理
      • 返回一个代表新增页面的Object(key),通常都是直接返回view本身就可以了,当然你也可以自定义自己的key,但是key和每个view要一一对应的关系。
  4. isViewFromObject()
    判断instantiateItem(Viewgroup,int)函数所返回的key与一个页面视图是否是代表的同一个视图(即他俩是否是对应的,对应的表示同一个view),通常我们直接写成return view==object
  5. destroyItem()
    移除一个给定位置的页面,适配器有责任从容器中删除这个视图,这是为了确保在finish update(viewgroup)返回时视图能够被移除。

adapter的代码:

package com.example.myviewpager;

import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;

import java.util.List;

public class MyAdapter extends PagerAdapter {

    private List<View> listview;

    public MyAdapter(List<View> listview) {
        this.listview = listview;
    }

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        container.addView(listview.get(position),0);
        return listview.get(position);
    }

    @Override
    public int getCount() {
        return listview.size();
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return view==object;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        container.removeView(listview.get(position));
    }
}

activity:


import android.view.LayoutInflater;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import androidx.viewpager.widget.ViewPager;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LayoutInflater lf=getLayoutInflater().from(this);
        View view1=lf.inflate(R.layout.layout1,null);
        View view2=lf.inflate(R.layout.layout2,null);
        View view3=lf.inflate(R.layout.layout3,null);
        List<View> viewList=new ArrayList<>();
        viewList.add(view1);
        viewList.add(view2);
        viewList.add(view3);
        ViewPager viewPager=findViewById(R.id.vp);//获取viewpager
        MyAdapter myAdapter=new MyAdapter(viewList);
        viewPager.setAdapter(myAdapter);
    }
}

ViewPager2

ViewPage2是Jetpack中的其中一个组件,可以实现滑动切换页面的效果,通常可以搭配其他组件实现banner切换、以及类似于抖音短视频上下滑动切换播放的效果。
ViewPager2是基于RecyclerView实现的,自然继承了RecyclerView的众多优点,并且针对ViewPager存在的问题做了优化

  • 支持垂直方向的滑动且实现及其简单
  • 完全支持RecyclerView的相关配置功能
  • 支持多个PageTransformer
  • 支持DiffUtil,局部数据刷新和item动画
  • 支持模拟用户滑动与禁止用户操作
  • ViewPager(旧支持库)本身并不直接支持懒加载。在ViewPager中,所有的页面都会在初始化时被预加载,即使用户可能不会立即浏览到它们。
    然而,可以通过自定义的方式在ViewPager中实现懒加载。一种常见的方法是在FragmentPagerAdapter或FragmentStatePagerAdapter中,重写instantiateItem()方法,并在该方法中控制页面的加载时机,使得只有当页面真正可见时才进行加载。
    ViewPager2(基于AndroidX库)则天生支持懒加载。ViewPager2使用RecyclerView作为其基础实现,可以利用RecyclerView的特性来实现懒加载。RecyclerView会根据可见区域来判断需要加载哪些项,从而实现了懒加载的效果。

什么是懒加载?

懒加载(Lazy Loading)是一种延迟加载数据或资源的策略,它在需要使用数据或资源时才进行加载,而不是在一开始就预加载。这种策略的目的是提高性能和资源利用效率。
例如:图片懒加载:在应用或网页中加载大量图片时,可以使用懒加载策略。当图片滚动到可见区域时,才开始加载该图片,而不是一次性加载所有图片。这样可以减少初始加载时间和网络带宽,并且避免同时加载大量图片导致的性能问题。

ViewPager与ViewPager2部分对比

在这里插入图片描述

常见api

//刷新Viewpager 同样支持recyclerView的局部刷新
notifyDataSetChanged()
 
setUserInputEnabled(false);//禁止手动滑动
 
setCurrentItem(0, false);//跳转到指定页面,false不带滚动动画
 
setCurrentItem(0);//跳转到指定页面,带滚动动画
 
addItemDecoration()//设置分割线 同RecyclerView
 
setOffscreenPageLimit();//设置预加载数量
 
setOrientation();//设置方向
 
fakeDragBy(offsetPx)//代码模拟用户滑动页面。支持通过编程方式滚动。
 
setPageTransformer()//设置滚动动画,参数可传 CompositePageTransformer,PageTransformer

动画

帧动画

Frame Animation
用多张图片来组成动画。一帧帧的播放图片,利用人眼视觉残留原理,给我们带来动画的感觉。它的原理的GIF图片、电影播放原理一样。
我们可以使用AnimationDrawable 来实现动画效果。

补间动画

Tween Animation
补间动画就是我们只需指定开始、结束的“关键帧“,而变化中的其他帧由系统来计算,不必自己一帧帧的去定义。
Android使用Animation代表抽象动画,包括四种子类:

  • AlphaAnimation(透明度动画)
  • ScaleAnimation(缩放动画)
  • TranslateAnimation(位移动画)
  • RotateAnimation(旋转动画)

一般都会采用动画资源文件来定义动画,把界面与逻辑分离
定义好anim文件后,我们可以通过AnimationUtils工具类来加载它们,加载成功后返回一个Animation。然后就可以通过View的startAnimation(anim)开始执行动画了。

属性动画

直接更改我们对象的属性。在上面提到的Tween Animation中,只是更改View的绘画效果而View的真实属性是不改变的
常用 Animator 类,ValueAnimator 等
Animator可加载动画资源文件
ValueAnimator可使用内置估值器,添加监听AnimatorUpdateListener,在每次变化时修改view的属性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/564564.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国内免费的Chatgpt网站分享 支持Ai对话绘图

Chatgpt正式进入大众视野&#xff0c;已半年有余&#xff0c;作为一款媲美于百度、谷歌搜索的工具&#xff0c;它已经成为我们工作、生活、学习中不可缺少的左膀右臂&#xff0c;相比于搜索引擎&#xff0c;它寻找答案&#xff0c;不再需要自己在众多模糊不定的结果中寻找自己需…

【生物信息】调控基因组学 (Regulatory Genomics) 和Deep CNN

文章目录 Regulatory GenomicsBiological motivation of Deep CNNMulti-task CNN 来自Manolis Kellis教授&#xff08;MIT计算生物学主任&#xff09;的课《人工智能与机器学习》 主要内容就是调控基因组学和深度卷积网络的结合 由于这部分在我学习的课程中内容很少&#xff0c…

使用虚拟机安装ikuai软路由系统,搭建pppoe拨号服务器

搭建pppoe拨号服务器 一、搭建ikuai软路由系统1、VMware版本2、ikuai官网上下载系统镜像3、使用虚拟机安装ikuai系统4、登录ikuai管理界面 二、安装win7虚拟机验证拨号功能三、其他电脑要使用这个pppoe虚拟机进行拨号怎么办呢&#xff1f; 一、搭建ikuai软路由系统 先说一下背景…

【C++/嵌入式笔试面试八股】一、11.C内存分配/堆栈

C内存分配/堆栈 01.C内存分配❤️ #include <stdio.h>const int g_A = 10; //常量区 int g_B = 20; //数据段 static<

冲冲冲冲冲

目录 java基础 面向对象 集合 线程 异常 IO 反射 MySQL SpringMVC 1.SpringMVC常用的注解有哪些&#xff1f; 2.说说你对Spring MVC的理解 Spring 1. spring是什么&#xff1f; 2.Autowired和Resource关键字的区别&#xff1f; 3.说说你对Spring的IOC是怎么理解的…

计算机硬件系统 — 冯诺依曼体系结构运行原理解析

目录 文章目录 目录计算机系统计算机硬件系统&#xff08;冯诺依曼体系结构&#xff09;PC 主机硬件CPU&#xff08;中央处理器&#xff09;CPU 的组成部分CPU 总线控制器单元运算器单元寄存器组超线程与多核架构三级高速缓存为什么需要缓存三级缓存结构 CPU 的指令集指令集的类…

IIS6.0 put文件上传GetShell

目录 WebDAV 环境配置 漏洞复现 漏洞修复 WebDAV WebDAV &#xff08;Web-based Distributed Authoring and Versioning&#xff09; 是一种HTTP1.1的扩展协议。它扩展了HTTP 1.1&#xff0c;在GET、POST、HEAD等几个HTTP标准方法以外添加了一些新HTTP请求方法&#xff0c…

生成模型(自编码器、VAE、GAN)

文章目录 自编码器Autoencoder潜在表示&#xff08;latent representation&#xff09;VAE迁移学习 生成对抗网络GAN李沐论文精读摘要导言相关工作Adversarial net简单总结 精读挖坑&#xff08;上课内容 来自Manolis Kellis教授&#xff08;MIT计算生物学主任&#xff09;的课…

14种UML图(统一建模语言)

目录 1.简述2.UML组成3.UML事物4.UML关系5.UML图5.1 UML图的分类5.2 结构图&#xff08;静态图&#xff09;1&#xff09;类图2&#xff09;对象图3&#xff09;构件图4&#xff09;部署图5&#xff09;制品图6&#xff09;包图7&#xff09;复合结构图 5.3 行为图&#xff08;…

k8s进阶4——安全机制常用工具之kube-beach、kube-hunter、Trivy、kubesec

文章目录 一、K8s安全机制二、kube-beach工具2.1 安装2.2 工具使用2.2.1 改成INFO状态2.2.2 改成pass状态 三、kube-hunter工具四、Trivy镜像漏洞扫描工具五、kubesec检查YAML文件安全配置 一、K8s安全机制 基本了解&#xff1a; 我们在前面学习的安全控制RBAC就属于K8s安全机制…

【Midjourney】Midjourney Prompt 提示词 ② ( 怀旧像素风 | 物体 A 被物体 B 包围 | 折纸艺术风格 )

文章目录 一、8-bit 16bit 提示词 - 怀旧像素风二、A out of B 提示词 - 物体 A 被物体 B 包围三、layered paper 提示词 - 折纸艺术风格 一、8-bit 16bit 提示词 - 怀旧像素风 使用 8-bit 16-bit 提示词 , 可以绘制出 像素游戏风格的图像 , 如下图所示 ; 该提示词适合创作与游…

Linux Web服务(HTTP HTML DNS)

DNS 域名解析 DNS负责将域名转换为IP地址。 详细的介绍在之前的博客中有详细记录。 这里回顾一下DNS的解析过程。 域名结构 主机名.子域.[二级域].顶级域.(根域) DNS 解析过程&#xff08;面试题&#xff09; 客户端 -> 本地缓存域名服务器 -> 根域服务器 -> 二级域服…

测试分析流程及输出项

测试分析 一、确认测试范围 根据测试项目的不同需求&#xff0c;有大致几类测试项目类型&#xff1a;商户平台功能测试、支付方式接入测试、架构调整类测试、后台优化测试、性能测试、基本功能自动化测试。 测试项目需要按照文档要求进行测试需求分析&#xff0c;并给出对应…

NIFI1.21.0最新版本安装_采用HTTP方式_搭建集群_实际操作---大数据之Nifi工作笔记0050

这里要提一嘴...看中文的,视频或者文档虽然学习会快一点,但是... 有的时候一些新的东西没有中文的,还是得看英文的...时间就了就好了,要不然解决不了问题 英文写的,凡是好东西,肯定是很详细的,并且就是为了让别人弄明白,做了大量解释,所以不用担心看不懂... 首先,把安装包,上…

Java的Arrays类的sort()方法(41)

目录 sort&#xff08;&#xff09;方法 1.sort&#xff08;&#xff09;方法的格式 2.使用sort&#xff08;&#xff09;方法时要导入的类 3.作用 4.作用的对象 5.注意 6.代码及结果 &#xff08;1&#xff09;代码 &#xff08;2&#xff09;结果 sort&#xff08;&…

redis【stream】:对redis流数据类型的详细介绍

目录 stream产生原因 stream的概念 stream底层实现 stream的常用指令 常用命令一览&#xff1a; xadd命令 xread命令 xlen命令 xrange命令 xrevrange命令 xtrim命令 xdel命令 xgroup命令 xinfo命令 xpending命令 xreadgroup命令 xack命令 xclaim命令 stream产…

集合的三种遍历方式

文章目录 目录 文章目录 一.迭代器遍历 二.增强for遍历 三. forEach方法 四. Lambda表达式 函数式接口: 函数式接口是指只有一个抽象方法的接口 为什么Lambda只能用于函数式接口 总结 前言 作者简介:最爱吃兽奶 座右铭:抱怨身处黑暗,不如提灯前行 内容介绍:今天给大家讲一下集合…

学完能拿下阿里23k的JMeter+Grafana+Influxdb搭建可视化性能测试监控平台

【背景说明】 使用jmeter进行性能测试时&#xff0c;工具自带的查看结果方式往往不够直观和明了&#xff0c;所以我们需要搭建一个可视化监控平台来完成结果监控&#xff0c;这里我们采用三种JMeterGrafanaInfluxdb的方法来完成平台搭建 【实现原理】 通过influxdb数据库存储…

Shell运维实战3-while、case、for、select

目录 case基本case 企业级案例 while 循环当型与直到后台运行while 实战 for & selectfor 循环方法linux 生成随机数select case 基本 请注意 case 使用后的闭合问题&#xff0c;开头 case&#xff0c;结尾 esac #! /bin/bashread -p "input your number:" ans…

【C++刷题集】-- day3

目录 选择题 单选 OR59 字符串中找出连续最长的数字串⭐ 【题目解析】 【解题思路】 JZ39 数组中出现次数超过一半的数字⭐ 【题目解析】 【解题思路1】 【解题思路2】 选择题 单选 1、以下程序的输出结果是 ( ) #include <stdio.h> int main() {char a[10] …