1 Star 2 Fork 0

xiyv / NEKOLINE

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
homework @ 39c9090
Loading...
README
MulanPSL-2.0

曦雨的项目

创建android studio项目

上传测试完毕

账号: test1 密码: test1

当前效果!!!!

img.png

生命周期

在 Android 中,Activity 的生命周期由一系列回调方法组成, 用于管理 Activity 的创建、启动、暂停、恢复、停止和销毁等不同状态。 以下是常见的 Activity 生命周期方法(调用时机):

回调流程参考官方文档:

关于图片形状

<com.flyjingfish.shapeimageviewlib.ShapeImageView/>属性

首先导包: io.github.FlyJingFish:ShapeImageView:1.4.9

尺寸	图像的四个角是圆角的`

shape_left_top_radius
尺寸	图像的左上角是圆角的

shape_right_top_radius
尺寸	图像右上角的圆角

shape_right_bottom_radius
尺寸	图像的右下角是圆角的

shape_left_bottom_radius 
尺寸	图像的左下角是圆角的

shape_start_top_radius
尺寸	图像左上角(Rtl:右上角)角圆角

shape_end_top_radius
尺寸	图像右上角(Rtl:左上角)角圆角

shape_end_bottom_radius
尺寸	图片右下角(Rtl:左下角)圆角

shape_start_bottom_radius
尺寸	图片左下角(Rtl:右下角)角圆角

shape_border
枚举	背景边框绘图形状为无绘图/矩形矩形/椭圆形圆形

shape_border_radius
尺寸	背景边框的四个角是圆形的

shape_border_left_top_radius
尺寸	背景边框的左上角是圆角的

shape_border_right_top_radius
尺寸	背景边框右上角的圆角

shape_border_right_bottom_radius
尺寸	背景边框的右下角是圆形的

shape_border_left_bottom_radius
尺寸	背景边框的左下角是圆形的

shape_border_start_top_radius
尺寸	背景边框左上角(Rtl:右上角)角圆角

shape_border_end_top_radius
尺寸	背景边框右上角(Rtl:左上角)角圆角

shape_border_end_bottom_radius
尺寸	背景边框右下角(Rtl:左下角)角圆角

shape_border_start_bottom_radius
尺寸	背景边框的左下角(Rtl:右下角)是圆形的

shape_border_color
颜色	背景边框绘图颜色

shape_border_gradient
布尔	背景边框是否以渐变色绘制

shape_border_startColor
颜色	背景边框绘制渐变颜色开始颜色

shape_border_centerColor
颜色	背景边框绘制渐变中间颜色

shape_border_endColor
颜色	背景边框绘制渐变色结束颜色

shape_border_angle
浮	背景边框绘制渐变颜色的起始角度

shape_border_rtl_angle
布尔	背景边框的渐变起始角度是否支持镜像 Rtl 适配

shape_border_strokeWidth
尺寸	背景边框绘图画笔的宽度

autoCrop_height_width_ratio
浮	图像纵横比是视图纵横比的倍数

小技巧

导入图片&图标

直接将图片放入drawable-xxhdpi等文件夹下,系统自动根据手机的分辨率选择 合适的图片。

图标一般选择svg图片,右击drawable文件夹,选择新建,再选择Vector Asset 在导入窗口中clip art是系统自带的icon,local-file则是导入本地svg或者psd 图标。关于图标,也可以放在mipmap文件夹中。

value文件夹

记录了各种值的引用的文件夹,方便修改。

1、attr.xml:

自定义类的属性,例如自定义的圆形图片类,该类的部分属性可以在此设置。

2、colors.xml

存放各种颜色的文件,若同一种颜色频繁使用,可以在这里定义此颜色,并使用 @colors/name来引用此颜色

3、dimens.xml

定义了各种长度及大小的数值,引用方法:@dimens/name

4、refs.xml

定义了对某些资源的引用, 引用方法:@refs/name

5、string.xml

定义TextView的文本,系统不推荐硬编码文本,推荐将文本统一存放在这里, 引用方法:android:text="@string/name"

6、themes.xml

定义某些通用的控件的样式,包括系统主题的样式 引用方法:style="@style/name"

跳转功能

核心方法:startActivity(),参数为一个意图Intent,以下为一个实例:

TextView buttonBack1 = findViewById(R.id.main_ButtonBack);
//寻找触发事件的控件main_ButtonBack
        buttonBack1.setOnClickListener(view -> { //setOnClickListener为控件设置监听,view为匿名类,此处已经简化
            startActivity(new Intent(RecyclerViewActivity.this, MainActivity.class));
            //Intent的第一个参数是所在的activity.this,表示出发点
            //第二个参数表示目的布局所在的activity,注意是后缀是.class!!!
        });

值得注意的是:涉及到的activity,必须在AndroidManifest.xml文件中注册才可以使用

将上述代码存放在一个方法中,在onCreate()初始化页面时,完成对控件的初始化,增加代码可读性

意图跨页传递数据

向下级页面传参

Intent不止可以进行页面跳转,也可以传递数据。

设置数据:putExtra(),取出:get类型Extra(),类型是你想要取的数据的类型

putExtra()的参数是键值对(key-value),第一个参数为名称,第二个为值

get()的参数一般只有一个,即是想要取的数据的名称,有时会设置第二个参数为默认值

向上级页面传参

上级页面在跳转时使用 :

private static final int REQUEST_CODE = 0x10;
startActivityForResult(intent, REQUEST_CODE);

其中 intent时意图,REQUEST_CODE是int型的请求码 下级页面完成事件时调用如下代码:

// 结果码
Intent data = new Intent();
data.putExtra("KEY_RESULT", "退出成功!");
setResult(Activity.RESULT_OK, data);
// 结束当前Activity,但是返回的是上一个页面,请注意使用
finish();

最后在上级页面中重写onActivityResult方法

结果码必须是Activity.RESULT_OK,代表返回成功

/**接收页面返回的结果@param requestCode  请求码@param resultCode   结果码@param data     返回数据集
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE) {
String result = data != null ? data.getStringExtra("KEY_RESULT") : "空字符串";
Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
Log.e("返回结果", result);
}
}
}

Toast提示

 Toast.makeText(MainActivity.this, "用户名或者密码错误", Toast.LENGTH_SHORT).show();
 //第一个参数为调用此方法的上下文即是所在的activity,第二个参数是提示文本,第三个参数是提示显示的时长
 //最后调用.show()将提示显示出来

RecycleView

Android 中的 recycleView 是一种视图容器,用于显示多个可重用的组件, 例如列表、网格和画廊。recycleView 可以自动管理组件的大小和布局,以便在内存和屏幕上最大化使用资源。

recycleView 的特点是可以重用组件,这意味着开发人员不需要手动创建和管理每个组件。 recycleView 还可以自动处理滚动和缩放,使开发人员可以专注于编写列表或画廊的 UI。

1、首先创建需要重用的组件布局

创建item_list.xml布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <com.example.demo1.CircleImageView
        android:id="@+id/group_avatar_img"
        android:layout_height="50dp"
        android:layout_width="50dp"
        android:src="@drawable/img_1"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        tools:ignore="MissingConstraints" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:layout_marginStart="10dp"
        android:textSize="20sp"
        android:textColor="#000000"
        android:text="@string/group_name"
        android:layout_toEndOf="@id/group_avatar_img"
        android:layout_alignTop="@id/group_avatar_img"
        />


    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:textSize="15sp"
        android:textColor="#000000"
        android:text="@string/last_msg"
        android:layout_toEndOf="@id/group_avatar_img"
        android:layout_below="@id/textView"
        android:maxLength="30"
        />

        <TextView
            android:id="@+id/textView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="10dp"
            android:layout_marginTop="20dp"
            android:textSize="15sp"
            android:textColor="#000000"
            android:text="@string/last_msg_date"
            android:layout_alignParentEnd="true"
            android:maxLength="30"
            />

    </RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

效果如图:

2、创建重用组件的实体类

public class News {
    public String title; // 标题
    public String content; //内容
    public int img; //图片
    public String date; //时间
}

图片资源是通过id获取,故设置为int,实际开发图片是通过后端返回,这里只做模拟数据

3、应用RecycleView布局

在activity_main.xml布局中添加如下代码

  <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="70dp"
        android:layout_marginBottom="70dp"
        android:paddingTop="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

此时布局效果如下:

此时布局内还未显示重用组件,默认显示item0到9

4、在MsgRecycleViewActivity中进行初始化

创建内部类MyAdapter(适配器)和MyViewHolder:

adapter是数据与ui之间的桥梁,它把后台数据与前端ui连接到一起,是一个展示数据的载体。

ViewHolder大部分都是通常出现在适配器里,为的就是listview滚动的时候能够快速设置值, 而不必每次都重新创建很多对象,从而提升性能。 在android开发中Listview是一个很重要的组件, 它以列表的形式根据数据的长自适应手机来展示具体内容,方便用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候, 会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。

优先写泛型MyViewHolder

其次onCreateViewHolder

再写getItemCount

最后写onBindViewHolder

   /**
     * 适配器类
     */
    class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
        /**
         * 初始化holder,将其与重用组件item_list.xml关联
         * @param parent The ViewGroup into which the new View will be added after it is bound to
         *               an adapter position.
         * @param viewType The view type of the new View.
         *
         * @return 数据源处理器
         */
        @NonNull
        @Override
        public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = View.inflate(MsgRecyclerViewActivity.this, R.layout.item_list, null);
            return new MyViewHolder(view);
        }

        /**
         * 使用holder动态设置绑定组件内容
         * @param holder The ViewHolder which should be updated to represent the contents of the
         *        item at the given position in the data set.
         * @param position The position of the item within the adapter's data set.
         */
        @Override
        public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
            News news = mNewsList.get(position);
            holder.mTitleTv.setText(news.title);
            holder.mTitleContent.setText(news.content);
            holder.circleImageView.setImageResource(news.img);
            holder.date.setText(news.date);
        }

        /**
         *
         * @return 返回数据的长度
         */
        @Override
        public int getItemCount() {
            return mNewsList != null ? mNewsList.size() : 0;
        }
    }
    /**
     * 自定义的ViewHolder
     */
    static class MyViewHolder extends RecyclerView.ViewHolder {
        TextView mTitleTv;      //这些属性的类型对应item_list.xml中的各个控件
        TextView mTitleContent;
        CircleImageView circleImageView;
        TextView date;

        /**
         * 将数据源与布局对应
         * @param itemView 数据源
         */
        public MyViewHolder(@NonNull View itemView) {   //构造函数
            super(itemView);
            mTitleTv = itemView.findViewById(R.id.textView);     //对应重用布局的群名
            mTitleContent = itemView.findViewById(R.id.textView2);    //对应最新消息
            circleImageView = itemView.findViewById(R.id.group_avatar_img);  //对应群头像
            date = itemView.findViewById(R.id.textView3);            //对应时间
        }
    }

在java代码中初始化数据并使用适配器和holder:

定义全局变量

    RecyclerView mRecyclerView;  //接收RecycleView控件
    MyAdapter mMyAdapter;        //适配器
    List<News> mNewsList = new ArrayList<>(); //数据列表

在onCreate()方法中添加:

        mRecyclerView = findViewById(R.id.recyclerview);  //绑定RecycleView控件
        // 构造一些数据
        @SuppressLint("SimpleDateFormat") SimpleDateFormat formatter = new SimpleDateFormat("HH:mm");//格式化时间
        for (int i = 0; i < 20; i++) {              //初始化模拟数据,真实开发通过后端返回
            News news = new News();
            news.title = "群聊" + i;
            news.content = "最新消息" + i;
            news.img = R.drawable.img_2;
            news.date =formatter.format( new Date());
            mNewsList.add(news);
        }
        mMyAdapter = new MyAdapter();
        mRecyclerView.setAdapter(mMyAdapter);     //为RecycleView设置适配器
        LinearLayoutManager layoutManager = new LinearLayoutManager(MsgRecyclerViewActivity.this);
        //列表布局,第二参数不指定是默认垂直滚动
        mRecyclerView.setLayoutManager(layoutManager);  //为RecycleView设置布局适配器

当该布局生成时,RecycleView就会产生列表数据,效果如下:

弹出框Dialog

生成一个最简单的弹出框

        AlertDialog.Builder builder = new AlertDialog.Builder(ProfileActivity.this);
        builder.setTitle("编辑资料");
        builder.setView(R.layout.create_user_dialog);  //弹出框的布局
        builder.setPositiveButton("确定",null);  
        builder.setNegativeButton("返回",null);
        AlertDialog alertDialog = builder.create();
        alertDialog.show();
        alertDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT,1000); //大小

效果图:

第三方库简化的适配器

首先引入第三方库:在setting.gradle中添加

可能因为android版本的问题,部分人需要在build.gradle中添加

 maven { url 'https://jitpack.io' }

然后在app目录下的build.gradle文件的依赖项里添加:

implementation "io.github.cymchad:BaseRecyclerViewAdapterHelper:3.0.11"

创建适配器类

关键是继承BaseQuickAdapter<实体类,BaseViewHolder>


/**
 * 第三方库使用RecyclerView的适配器
 */
public class TestAdapter extends BaseQuickAdapter<News, BaseViewHolder> {
    //构造函数,传入重用布局的资源id
    public TestAdapter(int layoutResId) {
        super(layoutResId);
    }

    /**
     *绑定数据
     * @param holder 数据处理器
     * @param news   数据实体类
     */
    @Override
    protected void convert(@NonNull BaseViewHolder holder, News news) {
        //第一个参数是需要绑定数据的控件,第二个参数是需要被绑定的数据
        holder.setText(R.id.textView, news.title).
                setText(R.id.textView2, news.content)
                .setText(R.id.textView3, news.date);
    }
}

使用适配器:

步骤与之前的基础用法相同

//定义全部变量
private TestAdapter adapter;
//然后在onCreate中初始化
mRecyclerView = findViewById(R.id.recyclerview);  //绑定RecycleView控件
        // 构造一些数据
        @SuppressLint("SimpleDateFormat") SimpleDateFormat formatter = new SimpleDateFormat("HH:mm");//格式化时间
        for (int i = 0; i < 20; i++) {              //初始化模拟数据,真实开发通过后端返回
            News news = new News();
            news.title = "群聊" + i;
            news.content = "最新消息" + i;
            news.img = R.drawable.img_2;
            news.date =formatter.format( new Date());
            mNewsList.add(news);
        }
        adapter = new TestAdapter(R.layout.item_list);
        mRecyclerView.setAdapter(adapter);     //为RecycleView设置适配器
        // 添加数据
        adapter.setList(mNewsList);
        // adapter 点击事件
        adapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) {
   
//                点击事件的逻辑代码
            }
        });
        LinearLayoutManager layoutManager = new LinearLayoutManager(MsgRecyclerViewActivity.this);
        //列表布局,第二参数不指定是默认垂直滚动
        mRecyclerView.setLayoutManager(layoutManager);  //为RecycleView设置布局适配器

值得注意的是,设置点击事件的方法有点小变化

方法从setOnClickListener变成了setOnItemClickListener

还有,重用的布局,是循环生成一整个布局,如过有高度宽度等需求,

必须自己手动设置好

相比之下确实已经简化了不少代码,同时也让适配器的概念清晰了不少。

底部导航栏实现

创建模板

右键包名-->新建-->Activity-->BottomNavigationActivity

语言选择java,其他的例如名字,自己取去吧!!

创建完成后,会在当前包下产生一个ui包,java文件BottomNavigationActivity

res文件夹下产生:

layout文件:

activity_main_bottom_navigation: 不同的fragment会在activity_main_bottom_navigation布局中替换它的fragment部分

fragment_home:第一个fragment的布局

fragment_dashboard:第二个fragment的布局

fragment_notifications:第三个fragment的布局

menu文件:bottom_nav_menu: 导航栏的样式

navigation文件:mobile_navigation: 导航栏与fragment布局的关联

需要添加固定控件的可以在该布局中添加

编写样式

在res/menu/bottom_nav_menu文件中编写样式

记得自己替换成想要的样式

<item
        android:id="@+id/navigation_home"
        android:icon="@drawable/msg"
        android:title="@string/title_home" />
    <item
        android:id="@+id/navigation_dashboard"
        android:icon="@drawable/friend"
        android:title="@string/title_dashboard" />
    <item
        android:id="@+id/navigation_notifications"
        android:icon="@drawable/space"
        android:title="@string/title_notifications" />

关联fragment

res/navigation/mobile_navigation中可以将fragment与导航栏进行关联

fragment意为一个片段,可以理解成为切换的一个视图,但其本质是Activity 的一部分,无论切换几次,都在该Activity的范围内

 <fragment
        android:id="@+id/navigation_home"
        android:name="com.example.demo1.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" />
<!--name属性对应Java类,对这个fragment进行初始化等操作
    layout属性对应其页面布局
-->

编写对应的布局文件

例如:fragment_home文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:background="@drawable/img_3"
    tools:context=".ui.home.HomeFragment">

    <include
        android:id="@+id/main_avatar_bar"
        layout="@layout/main_avatar_bar"
        />

    <TextView
        android:id="@+id/main_ButtonBack"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@drawable/back"
        android:layout_marginTop="10dp"
        tools:ignore="MissingConstraints,SpeakableTextPresentCheck"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginTop="70dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

其中main_avatar_bar为:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:background="@color/main_user_avatar_background"
    android:layout_height="70dp">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.example.demo1.CircleImageView
            android:id="@+id/main_avatar_img"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:src="@drawable/avatar"
            tools:ignore="MissingConstraints,SpeakableTextPresentCheck" />

        <TextView
            android:id="@+id/main_username"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:layout_marginStart="10dp"
            android:textSize="20sp"
            android:text="@string/user_name"
            android:layout_toEndOf="@id/main_avatar_img"
            android:layout_alignTop="@id/main_avatar_img"
            />


        <TextView
            android:id="@+id/main_user_status"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:textSize="12sp"
            android:text="@string/user_status"
            android:layout_toEndOf="@id/main_avatar_img"
            android:layout_below="@id/main_username"
            android:maxLength="30"
            />

    </RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

初始化fragment

进入ui包中fragment对应的java类:HomeFragment

记得修改onCreateView中的代码,将复杂的代码稍微简化一下

添加点击事件等功能应该在onViewCreated中完成

这个fragment是一个列表数据,关于本视图内的RecyclerView的使用,请参考前文

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.example.demo1.BottomNavigationActivity;
import com.example.demo1.CircleImageView;
import com.example.demo1.MainActivity;
import com.example.demo1.MsgRecyclerViewActivity;
import com.example.demo1.ProfileActivity;
import com.example.demo1.R;
import com.example.demo1.adapter.TestAdapter;
import com.example.demo1.databinding.FragmentHomeBinding;
import com.example.demo1.entity.News;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class HomeFragment extends Fragment {
    public RecyclerView recyclerView;
    public List<News> newsList = new ArrayList<>(); //数据列表
    public TestAdapter adapter;

    /**
     * 开始创建视图时进行的初始化
     * @param inflater The LayoutInflater object that can be used to inflate
     * any views in the fragment,
     * @param container If non-null, this is the parent view that the fragment's
     * UI should be attached to.  The fragment should not add the view itself,
     * but this can be used to generate the LayoutParams of the view.
     * @param savedInstanceState If non-null, this fragment is being re-constructed
     * from a previous saved state as given here.
     *
     * @return
     */
    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {

        return LayoutInflater.from(getContext()).inflate(R.layout.fragment_home, container, false);
    }

    /**
     * 视图创建完成后要完成的操作
     * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
     * @param savedInstanceState If non-null, this fragment is being re-constructed
     * from a previous saved state as given here.
     */
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        recyclerView = view.findViewById(R.id.recyclerview);
        adapter = new TestAdapter(R.layout.item_list);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        //因为是在fragment中,无法直接使用类名.this获取上下文,故使用getContext()
        loadData();
        // adapter 点击事件
        adapter.setOnItemClickListener((adapter, view1, position) -> {
//                startActivity(new Intent(MsgRecyclerViewActivity.this, ));
            Toast.makeText(getContext(), "点击了列表项!", Toast.LENGTH_SHORT).show();
        });
        backToLogin(view);
        goToProfile(view);
    }

    /**
     * 加载数据
     */
    private void loadData(){
        @SuppressLint("SimpleDateFormat") SimpleDateFormat formatter = new SimpleDateFormat("HH:mm");//格式化时间
        for (int i = 0; i < 20; i++) {              //初始化模拟数据,真实开发通过后端返回
            News news = new News();
            news.title = "群聊" + i;
            news.content = "最新消息" + i;
            news.img = R.drawable.img_2;
            news.date =formatter.format( new Date());
            newsList.add(news);
        }
       adapter.setList(newsList);
    }

    /**
     * 返回登录页按钮
     * @param view 上下文
     */
    public void backToLogin(View view) {
        TextView buttonBack1 = view.findViewById(R.id.main_ButtonBack);
        buttonBack1.setOnClickListener(view1 -> {
            startActivity(new Intent(getContext(), MainActivity.class));
        });
    }

    /**
     * 跳转个人信息页按钮
     * @param view 上下文
     */
    public void goToProfile(View view){
        CircleImageView buttonBack1 = view.findViewById(R.id.main_avatar_img);
        buttonBack1.setOnClickListener(view1 -> {
            startActivity(new Intent(getContext(), ProfileActivity.class));
        });
    }

}

编写BottomNavigationActivity

去除部分不需要的代码后:

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.NavigationUI;

import com.google.android.material.bottomnavigation.BottomNavigationView;

public class BottomNavigationActivity extends AppCompatActivity {


    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_bottom_navigation);
        BottomNavigationView navView = findViewById(R.id.nav_view);

        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main_bottom_navigation);
        NavigationUI.setupWithNavController(navView, navController);

    }

}

Tab栏目的实现

导入相关依赖并创建模板

implementation 'com.github.bumptech.glide:glide:4.15.1'

右键->新建->activity->TabbedActivity

在你的ui包中会多出三个java类,res/layout中多出两个tab相关的布局文件

SectionsPagerAdapter类:

页面适配器,调整页面的标题,个数等.请根据自身要求和 本文档代码简化自动生成的代码

import android.content.Context;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;

/**
 * A [FragmentPagerAdapter] that returns a fragment corresponding to
 * one of the sections/tabs/pages.
 */
public class SectionsPagerAdapter extends FragmentPagerAdapter {

    private String[] tabs = new String[]{"精选","热门","推荐"};

    /**
     * 构造函数
     * @param context 上下文
     * @param fm 片段
     */
    public SectionsPagerAdapter(Context context, FragmentManager fm) {
        super(fm);
    }

    /**
     * 获取对应位置的fragment
     * @param position fragment下标
     * @return 对应的fragment
     */
    @Override
    public Fragment getItem(int position) {
        // getItem is called to instantiate the fragment for the given page.
        // Return a TabFragment (defined as a static inner class below).
        return TabFragment.newInstance(position + 1);
    }

    /**
     * 获取页面的标题
     * @param position 标题下标
     * @return 页面标题
     */
    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return tabs[position];
    }

    /**
     * 获取页面个数
     * @return 页面个数
     */
    @Override
    public int getCount() {
        // Show 2 total pages.
        return tabs.length;
    }
}

TabFragment类:

对fragment进行配置,无论有多少个标题和页面,fragment都只有一个, 每个页 面大体相同,不同的是数据,将数据进行替换就可以达到不同页面的效果(这种替换方法 只适合比较简单的分类等功能)

首先先将该类简化到如下,后续涉及页面代码时再回来补全

   private static final String ARG_SECTION_NUMBER="section_number";

    /*
      创建实例的函数
      @param index 要展示的页面的下标
     * @return fragment片段
     */
public static TabFragment newInstance(int index){
        TabFragment fragment=new TabFragment();
        Bundle bundle=new Bundle();
        bundle.putInt(ARG_SECTION_NUMBER,index);
        fragment.setArguments(bundle);
        return fragment;
}

inflater它的作用是将一个layout.xml布局文件变为一个View对象。 尤其在ListView、 GridView、RecyclerView的Adapter还有组合自定义控件中,我们都会使 用 inflate () 方法去加载一个布局,作为每个Item的布局。

Activity类:简单简化即可

import android.os.Bundle;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;

import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity;

import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

import com.example.demo1.ui.main.SectionsPagerAdapter;
import com.example.demo1.databinding.ActivityTabBinding;

public class TabActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_tab);
        SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
        ViewPager viewPager = findViewById(R.id.view_pager);
        viewPager.setAdapter(sectionsPagerAdapter);
        TabLayout tabs = findViewById(R.id.tabs);
        tabs.setupWithViewPager(viewPager);

    }
}

初始化Tabs布局

fragment_tab.xml:

片段布局,即是每个不同页面的大致布局。 因为是仿照tx视频的分类页面,因此本布局只需要一个RecyclerView控件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/constraintLayout"
    android:layout_width="match_parent" 
    android:layout_height="match_parent"
    tools:context=".ui.main.TabFragment">

    <androidx.recyclerview.widget.RecyclerView 
        android:id="@+id/recycler_view"
        android:layout_width="match_parent" 
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

activity_tab.xml:

Tab的大体布局,请注意app:tabMode="scrollable"属性,若是选择了fixed, 则全部title将平分屏幕的空间,不加该属性默认自动,当选项卡数量较少且可以适应屏幕宽度时, 将使用固定模式;当选项卡数量较多或无法适应屏幕宽度时,将使用可滚动模式。

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:background="@color/white"
    tools:context=".TabActivity">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabMode="scrollable"
        tools:ignore="SpeakableTextPresentCheck" />

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="48dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:ignore="SpeakableTextPresentCheck" />


</androidx.coordinatorlayout.widget.CoordinatorLayout>

内容布局

本部分与第三方库使用RecyclerView几乎一样,所以会快速带过

创建item_video.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="vertical"
    android:layout_margin="10dp"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_height="wrap_content">
    <ImageView
        android:id="@+id/img_video"
        tools:src="@drawable/avatar"
        android:scaleType="centerCrop"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        tools:ignore="MissingConstraints" />
    <TextView
        android:id="@+id/tv_video_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/black"
        android:textSize="16sp"
        tools:text="@string/user_name"
        android:gravity="center"/>

</LinearLayout>

预期效果图如下:

创建Video实体类:

public class Video {
    private String name;
    private String imgUrl;

    public Video(String name, String imgUrl) {
        this.name = name;
        this.imgUrl = imgUrl;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getImgUrl() {
        return imgUrl;
    }

    public void setImgUrl(String imgUrl) {
        this.imgUrl = imgUrl;
    }
}

请注意,这里的图片使用的String类型,使用网络图片的url地址作为资源。 安卓开发不能像html一样直接使用网络引用url,必须先添加网络权限。

添加网络权限:

在AndroidManifest.xml的manifest标签下添加

    <uses-permission
        android:name="android.permission.INTERNET"
        tools:ignore="ManifestOrder" />

创建VideoAdapter适配器:


import android.widget.ImageView;

import androidx.annotation.NonNull;

import com.bumptech.glide.Glide;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.viewholder.BaseViewHolder;
import com.example.demo1.R;
import com.example.demo1.entity.Song;

public class VideoAdapter extends BaseQuickAdapter<Video, BaseViewHolder> {
    public VideoAdapter(int layoutResId) {
        super(layoutResId);
    }

    @Override
    protected void convert(@NonNull BaseViewHolder holder, Video song) {
        holder.setText(R.id.tv_video_name, song.getName());
        ImageView imageView = holder.getView(R.id.img_video);
        Glide.with(getContext()).load(song.getImgUrl()).into(imageView);
    }
}

在TabFragment中初始化:

创建页面

/**
     *将布局转化为一个view对象
     * @param inflater 用于将布局转化为view对象
     * @param container
     * 如果非 null,则这是片段的 UI 应附加到的父视图。 片段不应添加视图本身,但这可用于生成视图的 LayoutParams。
     * @param savedInstanceState 如果非 null,则此片段将从先前保存的状态重新构造,如此处给出。
     * @return view对象
     */
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return LayoutInflater.from(getContext()).inflate(R.layout.fragment_tab, container, false);
    }

初始化内容:

 /**
     * 创建视图完成后的初始化工作
     * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
     * @param savedInstanceState 如果非 null,则此片段将从先前保存的状态重新构造,如此处给出。
     */
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        RecyclerView recyclerView = view.findViewById(R.id.recycler_view);

        // 设置网格状,因为默认是纵向滚动,所以2代表2列网格
        GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 2);
        recyclerView.setLayoutManager(layoutManager);

        adapter = new VideoAdapter(R.layout.item_video);
        recyclerView.setAdapter(adapter);

        loadData();
    }

    /**
     * 加载数据
     */
    private void loadData() {
        List<Video> songs = new ArrayList<>();

        Video song = new Video("电影1", "https://puui.qpic.cn/vcover_vt_pic/0/mzc0020050ixd7l1677735885503/350?max_age=7776001");
        Video video1 = new Video("电影2", "https://puui.qpic.cn/vcover_vt_pic/0/mzc002009nbjfwn1657179757077/350?max_age=7776001");
        Video video2 = new Video("电影3", "https://puui.qpic.cn/vcover_vt_pic/0/mzc00200qwmqz011684115814211/350?max_age=7776001");
        Video video3 = new Video("电影4", "https://puui.qpic.cn/vcover_vt_pic/0/mzc002000umo4761658894202518/350?max_age=7776001");
        Video video4 = new Video("电影5", "https://puui.qpic.cn/vcover_vt_pic/0/nbusftujqwczi7y1618415148198/350?max_age=7776001");
        Video video5 = new Video("电影6", "https://puui.qpic.cn/vcover_vt_pic/0/mzc00200zlh907s1660549999040/350?max_age=7776001");
        Video video6 = new Video("电影7", "https://puui.qpic.cn/vcover_vt_pic/0/z6j3ixjjcokafyc1566896570/350?max_age=7776001");

        songs.add(song);
        songs.add(video1);
        songs.add(video2);
        songs.add(video3);
        songs.add(video4);
        songs.add(video5);
        songs.add(video6);

        adapter.setList(songs);
    }

最终效果展示

网络请求

1.什么是Retrofit

retrofit是现在比较流行的网络请求框架,可以理解为okhttp的加强版,底层封装了Okhttp准确来说,Retrofit是一个RESTful的http网络请求框架的封装。 因为网络请求工作本质上是由okhttp来完成,而Retrofit负责网络请求接口的封装。

本质过程:App应用程序通过Retrofit请求网络,实质上是使用Retrofit接口层封装请求参数、 Header、Url等信息,之后由okhttp来完成后续的请求工作。在服务端返回数据后,okhttp将原始数据交给Retrofit,Retrofit根据用户需求解析。


2.Retrofit的优点

  • 超级解耦 ,接口定义、接口参数、接口回调不在耦合在一起
  • 可以配置不同的httpClient来实现网络请求,如okhttp、httpclient
  • 支持同步、异步、Rxjava
  • 可以配置不同反序列化工具类来解析不同的数据,如json、xml
  • 请求速度快,使用方便灵活简洁

注解

请求方法注解

Retrofit使用大量注解来简化请求,Retrofit将okhttp请求抽象成java接口,使用注解来配置和描述网络请求参数。大概可以分为以下几类,我们先来看看各个注解的含义,再一一去实践解释。

请求方法注解 说明
@GET get请求
@POST post请求
@PUT put请求
@DELETE delete请求
@PATCH patch请求,该请求是对put请求的补充,用于更新局部资源
@HEAD head请求
@OPTIONS options请求
@HTTP 通过注解,可以替换以上所有的注解,它拥有三个属性:method、path、hasBody

请求头注解
请求头注解 说明
@Headers 用于添加固定请求头,可以同时添加多个,通过该注解的请求头不会相互覆盖,而是共同存在
@Header 作为方法的参数传入,用于添加不固定的header,它会更新已有请求头

请求参数注解
请求参数注解 说明
@Body 多用于Post请求发送非表达数据,根据转换方式将实例对象转化为对应字符串传递参数,比如使用Post发送Json数据,添加GsonConverterFactory则是将body转化为json字符串进行传递
@Filed 多用于Post方式传递参数,需要结合@FromUrlEncoded使用,即以表单的形式传递参数
@FiledMap 多用于Post请求中的表单字段,需要结合@FromUrlEncoded使用
@Part 用于表单字段,Part和PartMap与@multipart注解结合使用,适合文件上传的情况
@PartMap 用于表单字段,默认接受类型是Map<String,RequestBody>,可用于实现多文件上传
@Path 用于Url中的占位符
@Query 用于Get请求中的参数
@QueryMap 与Query类似,用于不确定表单参数
@Url 指定请求路径

请求和响应格式(标记)注解
标记类注解 说明
@FromUrlCoded 表示请求发送编码表单数据,每个键值对需要使用@Filed注解
@Multipart 表示请求发送form_encoded数据(使用于有文件上传的场景),每个键值对需要用@Part来注解键名,随后的对象需要提供值
@Streaming 表示响应用字节流的形式返回,如果没有使用注解,默认会把数据全部载入到内存中,该注解在下载大文件时特别有用

Retrofit的使用

我们先来解释注解的使用和需要注意的问题(具体的使用步骤后面会给出)。上面提到注解是用来配置和描述网络请求参数的,我们来逐个讲解一下,首先创建网络接口类:

  • Retrofit将okhttp请求抽象成java接口,采用注解描述和配置网络请求参数,用动态代理将该接口的注解“翻译”成一个Http请求,最后执行Http请求。
  • 注意:接口中的每个方法的参数都要用注解标记,否则会报错。
1.注解详解

@GET、@Query、@QueryMap的使用
public interface Api {
    //get请求
    @GET("user")
    Call<ResponseBody> getData();
}
  • @GET 请求方法注解,get请求,括号内的是请求地址,Url的一部分
  • Call<*> 返回类型,*表示接收数据的类,一般自定义
  • getData() 接口方法名称,括号内可以写入参数

上面的网络接口最简单的一种形式,我们先从简单的开始,一步步深入了解。这是一个没有网络参数的get请求方式,需要在方法头部添加@GET注解,表示采用get方法访问网络请求,括号内的是请求的地址(Url的一部分) ,其中返回类型是Call<*>,*表示接收数据的类,如果想直接获取ResponseBody中的内容,可以定义网络请求返回值为Call<ResponseBody>,ResponseBody是请求网络后返回的原始数据,如果网络请求没有参数,不用写。

这里特别说明Url的组成(下面会讲解到),retrofit把网络请求的Url分成两部分设置:第一部分在创建Retrofit实例时通过.baseUrl()设置,第二部分在网络接口注解中设置,比如上面接口的"/user",网络请求的完整地址Url = Retrofit实例.baseUrl()+网络请求接口注解()

下面我们来看看有参数的get请求方法:

 @GET("user")
 Call<ResponseBody> getData2(@Query("id") long idLon, @Query("name") String nameStr);
  • @Query 请求参数注解,用于Get请求中的参数
  • "id"/"name" 参数字段,与后台给的字段需要一致
  • long/String 声明的参数类型
  • idLon/nameStr 实际参数

添加参数在方法括号内添加@Query,后面是参数类型和参数字段,表示后面idLon的取值作为"id"的值,nameStr的取值作为"name"的值,其实就是键值对,Retrofit会把两个字段拼接到接口中,追加到"/user"后面。比如:baseUrl为 api.github.com/ ,那么拼接网络接口注解中的地址后变为:api.github.com/user ,我们需要传入的id=10006,name="刘亦菲",那么拼接参数后就是完整的请求地址:https://api.github.com/user?id=10006&name=刘亦菲

 @GET("user")
 Call<ResponseBody> getData3(@QueryMap Map<String, Object> map);
  • @QueryMap 请求参数注解,与@Query类似,用于不确定表单参数
  • Map<String, Object> map 通过Map将不确定的参数传入,相当于多个Query参数 如果有不确定的把表单参数我们可以使用@QueryMap注解来添加参数,通过Map将不确定的参数存入,然后在方法体中带给网络接口,相当于多个Query参数,看看下面的使用:
 Map<String, Object> map = new HashMap<>();
  map.put("id", 10006);
  map.put("name", "刘亦菲");

  Call<ResponseBody> call = retrofit.create(Api.class).getData3(map);

@POST、@FormUrlEncoded、@File、@FileMap、@Body的使用

我们来看看没有参数的post方法的请求:

@POST("user/emails")
Call<ResponseBody> getPostData();
  • @POST 请求方法注解,表示采用post方法访问网络请求,括号后面是部分的URL地址

post请求网络方法,需要在方法头部添加@POST注解,表示采用post方法访问网络请求,这里是没有参数的,那有参数的Post请求方法该怎样写呢?

@FormUrlEncoded
@POST("user/emails")
Call<ResponseBody> getPostData2(@Field("name") String nameStr, @Field("sex") String sexStr);
  • @FormUrlEncoded 请求格式注解,请求实体是一个From表单,每个键值对需要使用@Field注解
  • @Field 请求参数注解,提交请求的表单字段,必须要添加,而且需要配合@FormUrlEncoded使用
  • "name"/"sex" 参数字段,与后台给的字段需要一致
  • String 声明的参数类型
  • nameStr/sexStr 实际参数,表示后面nameStr的取值作为"name"的值,sexStr的取值作为"sex"的值

Post请求如果有参数需要在头部添加@FormUrlEncoded注解,表示请求实体是一个From表单,每个键值对需要使用@Field注解,使用@Field添加参数,这是发送Post请求时,提交请求的表单字段,必须要添加的,而且需要配合@FormUrlEncoded使用

 @FormUrlEncoded
 @POST("user/emails")
 Call<ResponseBody> getPsotData3(@FieldMap Map<String, Object> map);
 Map<String, Object> fieldMap = new HashMap<>();
 map.put("name", "刘亦菲");
 map.put("sex", "女");

 Call<ResponseBody> psotData3 = retrofit.create(Api.class).getPostData3(map);
  • @FieldMap 请求参数注解,与@Field作用一致,用于不确定表单参数
  • Map<String, Object> map 通过Map将不确定的参数传入,相当于多个Field参数

当有多个不确定参数时,我们可以使用@FieldMap注解,@FieldMap与@Field的作用一致,可以用于添加多个不确定的参,类似@QueryMap,Map的key作为表单的键,Map的value作为表单的值。

适用于Post请求的还有一个注解@Body,@Body可以传递自定义类型数据给服务器,多用于post请求发送非表单数据,比如用传递Json格式数据,它可以注解很多东西,比如HashMap、实体类等,我们来看看它用法:

@POST("user/emails")
Call<ResponseBody> getPsotDataBody(@Body RequestBody body);
  • @Body 上传json格式数据,直接传入实体它会自动转为json,这个转化方式是GsonConverterFactory定义的

特别注意:@Body注解不能用于表单或者支持文件上传的表单的编码,即不能与@FormUrlEncoded和@Multipart注解同时使用,否则会报错。

我们找到@Body的源码发现,isFormEncoded为true或者isMultipart为ture时,满足其中任何一个条件都会抛出该异常,通过变量名字可以看出使用@ FormUrlEncoded 或者@Multipart 标签。

 if (annotation instanceof Body) {
    if (isFormEncoded || isMultipart) {
       throw parameterError(method, p,
          "@Body parameters cannot be used with form or multi-part encoding.");
     }
  }

@Http

@HTTP注解的作用是替换@GET、@POST、@PUT、@DELETE、@HEAD以及更多拓展功能

@HTTP(method = "GET", path = "user/keys", hasBody = false)
Call<ResponseBody> getHttpData();
  • method 表示请求的方法,区分大小写,这里的值retrofit不会再做任何处理,必须要保证正确
  • path 网络请求地址路径
  • hasBody 是否有请求体,boolean类型

@HTTP注解可以通过method字段灵活设置具体请求方法,通过path设置网络请求地址,用的比较少。@HTTP替换@POST、@PUT、@DELETE、@HEAD等方法的用法类似,这里就不一一讲解了。


@Path
 @GET("orgs/{id}")
 Call<ResponseBody> getPathData(@Query("name") String nameStr, @Path("id") long idLon);
  • @Query get请求方法参数的注解,上面已经解释了,这里就不重复讲
  • @Path 请求参数注解,用于Url中的占位符{},所有在网址中的参数

@Path注解用于Url中的占位符{},所有在网址中的参数,如上面 @GET("orgs/{id}")的id,通过{}占位符来标记id,使用@Path注解传入idLon的值,注意有的Url既有占位符又有"?"后面的键值对,其实@Query和@Path两者是可以共用的。在发起请求时,{id}会被替换为方法中第二个参数的值idLon。


@Url

如果需要重新地址接口地址,可以使用@Url,将地址以参数的形式传入即可。如果有@Url注解时,GET传入的Url可以省略。

@GET
Call<ResponseBody> getUrlData(@Url String nameStr, @Query("id") long idLon);
  • @Url 表示指定请求路径,可以当做参数传入

@Header、@Headers

我们可以在方法参数内添加请求头,@Header用于添加不固定的请求头,作用于方法的参数,作为方法的参数传入,该注解会更新已有的请求头。

 @GET("user/emails")
 Call<ResponseBody> getHeaderData(@Header("token") String token);

@header 请求头注解,用于添加不固定请求头 我们想对某个方法添加固定请求头时可以参考下面的写法,@headers用于添加固定的请求头,作用于方法,可以同时添加多个,通过该注解添加的请求头不会相互覆盖,而是共同存在。

 @Headers({"phone-type:android", "version:1.1.1"})
 @GET("user/emails")
 Call<ResponseBody> getHeadersData();
  • @headers 请求头注解,用于添加固定请求头,可以添加多个

@Streaming
 @Streaming
 @POST("gists/public")
 Call<ResponseBody> getStreamingBig();
  • @Streaming 表示响应体的数据用流的方式返回,使用于返回数据比较大,该注解在下载大文件时特别有用 我们在使用下载比较大的文件的时候需要添加@Streaming注解

@Multipart、@part、@PartMap
@Multipart
@POST("user/followers")
Call<ResponseBody> getPartData(@Part("name") RequestBody name, @Part MultipartBody.Part file);
  • @Multipart 表示请求实体是一个支持文件上传的表单,需要配合@Part和@PartMap使用,适用于文件上传
  • @Part 用于表单字段,适用于文件上传的情况,@Part支持三种类型:RequestBody、MultipartBody.Part、任意类型
  • @PartMap 用于多文件上传, 与@FieldMap和@QueryMap的使用类似

上面的使用是一个上传文字和文件的写法,在使用@Part注解时需要在头部添加@Multipart注解,实现支持文件上传,我们来看看上面代码怎么使用:

//声明类型,这里是文字类型
 MediaType textType = MediaType.parse("text/plain");
//根据声明的类型创建RequestBody,就是转化为RequestBody对象
RequestBody name = RequestBody.create(textType, "这里是你需要写入的文本:刘亦菲");

//创建文件,这里演示图片上传
File file = new File("文件路径");
if (!file.exists()) {
   file.mkdir();
 }

//将文件转化为RequestBody对象
//需要在表单中进行文件上传时,就需要使用该格式:multipart/form-data
RequestBody imgBody = RequestBody.create(MediaType.parse("image/png"), file);
//将文件转化为MultipartBody.Part
//第一个参数:上传文件的key;第二个参数:文件名;第三个参数:RequestBody对象
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), imgBody);

Call<ResponseBody> partDataCall = retrofit.create(Api.class).getPartData(name, filePart);

首先声明类型,然后根据类型转化为RequestBody对象,返回RequestBody或者转化为 MultipartBody.Part,需要在表单中进行文件上传时,就需要使用该格式:multipart/form-data

@PartMap的使用与@FieldMap和@QueryMap的使用类似,用于多文件上传,我们直接看代码:

 @Multipart
 @POST("user/followers")
 Call<ResponseBody> getPartMapData(@PartMap Map<String, MultipartBody.Part> map);
File file1 = new File("文件路径");
    File file2 = new File("文件路径");
        if (!file1.exists()) {
        file1.mkdir();
    }
        if (!file2.exists()) {
        file2.mkdir();
    }

    RequestBody requestBody1 = RequestBody.create(MediaType.parse("image/png"), file1);
    RequestBody requestBody2 = RequestBody.create(MediaType.parse("image/png"), file2);
    MultipartBody.Part filePart1 = MultipartBody.Part.createFormData("file1", file1.getName(), requestBody1);
    MultipartBody.Part filePart2 = MultipartBody.Part.createFormData("file2", file2.getName(), requestBody2);

    Map<String,MultipartBody.Part> mapPart = new HashMap<>();
        mapPart.put("file1",filePart1);
        mapPart.put("file2",filePart2);

    Call<ResponseBody> partMapDataCall = retrofit.create(Api.class).getPartMapData(mapPart);

上面的(@PartMap Map<String, MultipartBody.Part> map)方法参数中的 MultipartBody.Part 可以是RequestBody、String、MultipartBody.Part等类型,可以根据个人需要更换,这里就不一一说明了。


2.retrofit在项目中简单使用(get请求)
在清单文件中添加网络权限

retrofit是网络请求框架,毫无疑问必须要在AndroidManifest.xml文件中添加网络权限

<uses-permission android:name="android.permission.INTERNET"/>

image.png

添加retrofit的依赖库

在app目录下的build.gradle文件中添加Retrofit依赖库,由于Retrofit已经封装有okhttp所以不需要再添加okhttp的库

implementation 'com.squareup.retrofit2:retrofit:2.5.0'

创建接受服务器返回数据的类

在正式的请求网络数据中,返回的数据的外嵌套部分都是一样的,携带状态码、状态信息、实体数据一起返回,我们可以将它封装一个统一的数据回调类。

public class Data<T> {
    private int code;
    private String message;
    private T data;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

创建实体类用以接受返回的数据
package com.example.demo1.entity;
public class Video {
    private String name;
    private String picurl;

    public Video(String name, String picurl) {
        this.name = name;
        this.picurl = picurl;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPicurl() {
        return picurl;
    }

    public void setPicurl(String picurl) {
        this.picurl = picurl;
    }
}

创建用于描述网络接口的类
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.POST;
import retrofit2.http.Query;

public interface Api {
    //get请求
    @GET("api/rand.music")
    Call<Data<Video>> getJsonData(@Query("sort") String sort, @Query("format") String format);
    //接口的参数应与后端保持一直,包括参数名称

}

在方法头部添加@GET注解,表示采用get方法访问网络请求,括号内的是请求的地址(Url的一部分) ,其中返回类型是Call<Video>,Video表示接收数据的类,通常是自定义的类,Data是上面封装的一个接收数据的统一公共类,添加参数在方法括号内添加@Query,后面是参数类型和参数字段,其实就是键值对的形式。


创建Retrofit实例

在任意Activity或者fragment中实例化:

private Retrofit retrofit;
private Api api;
public void initRequest() {
        retrofit = new Retrofit.Builder()
                //设置网络请求BaseUrl地址
                 .baseUrl("https://api.uomg.com/")
                //设置数据解析器
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        //创建网络请求接口对象实例
        api = retrofit.create(Api.class);
    }

此处我在fragment中使用一个方法封装初始化请求部分retrofitapi都是全局变量,通过new Retrofit.Builder().buile()构建Retrofit实例对象,同时设置baseUrl地址,Retrofit把网络请求的URL 分成了两部分设置: 第一部分:在创建Retrofit实例时通过.baseUrl()设置,baseUrl地址必须以/结尾,否则会报错。

第二部分:在网络请求接口的注解设置,就是在上面的APi接口中用GET注解的字符串:

@GET("api/rand.music")

上面地址拼接后的完整地址为:httpsf://api.uomg.com/api/rand.music?srot=新歌榜&format=json,然后通过addConverterFactory设置数据解释器,这里添加的是Gson解释器。这是为了使来自接口的json结果会自动解析成定义好的字段和类型都相符的json对象接受类,在Retrofit 2.0中已经没有Converter,需要自己创建一个Converter, 不然Retrofit只能接收字符串结果,最后需要自己去解析。

在app目录下的build.gradle中添加Converter依赖:

implementation 'com.squareup.retrofit2:converter-gson:2.0.2'

Retrofit支持多种数据解析,需要在Gradle添加依赖:

数据解析器 Gradle依赖
Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson com.squareup.retrofit2:converter-jackson:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
Moshi com.squareup.retrofit2:converter-moshi:2.0.2
Wire com.squareup.retrofit2:converter-wire:2.0.2
Scalars com.squareup.retrofit2:converter-scalars:2.0.2

创建网络请求接口实例
public void doNetwork(View view) {        TextView buttonBack1 = view.findViewById(R.id.text_dashboard);
        buttonBack1.setOnClickListener(view1 -> {
            Toast.makeText(getContext(), "点击了按钮!", Toast.LENGTH_SHORT).show();
            //对发送请求进行封装
            Call<Data<Video>> dataCall = api.getJsonData("新歌榜", "json");
}

调用Retrofit实例对象 create() 方法,传入网络接口类,得到接口对象实例,调用接口类中的方法。


发送网络请求(同步/异步)
public void doNetwork(View view) {        TextView buttonBack1 = view.findViewById(R.id.text_dashboard);
        buttonBack1.setOnClickListener(view1 -> {
            Toast.makeText(getContext(), "点击了按钮!", Toast.LENGTH_SHORT).show();
            //对发送请求进行封装
            Call<Data<Video>> dataCall = api.getJsonData("新歌榜", "json");
             //异步请求
            dataCall.enqueue(new Callback<Data<Video>>() {
                  //请求成功回调
                  @Override
                  public void onResponse(Call<Data<Video>> call, Response<Data<Video>> response) {
                     }
                 //请求失败回调
                 @Override
                 public void onFailure(Call<Data<Video>> call, Throwable t) {   
                     }
            });
}

处理返回的数据
/**
* 定义网络请求按钮
* @param view fragment页面
*/
public void doNetwork(View view) {
        TextView buttonBack1 = view.findViewById(R.id.text_dashboard);
        buttonBack1.setOnClickListener(view1 -> {
            Toast.makeText(getContext(), "点击了按钮!", Toast.LENGTH_SHORT).show();
            //对发送请求进行封装
            Call<Data<Video>> dataCall = api.getJsonData("新歌榜", "json");
            //处理请求数据
            dataCall.enqueue(new Callback<Data<Video>>() {
                @SuppressLint("SetTextI18n")
                @Override
                public void onResponse(Call<Data<Video>> call, Response<Data<Video>> response) {
                    Data<Video> body = response.body();
                    if (body == null) return;
                    Video song = body.getData();
                    if (song == null) return;
                    TextView tv_username = view.findViewById(R.id.tv_username);
                    tv_username.setText(song.getName());
                    ImageView img = view.findViewById(R.id.img_video_test);
                    if (song.getPicurl() == null)
                        img.setImageResource(R.drawable.avatar);
                    else {
                        Glide.with(Objects.requireNonNull(getContext())).load(song.getPicurl()).into(img);
                    }
                    Toast.makeText(getContext(), "get回调成功:异步执行", Toast.LENGTH_SHORT).show();
                }

                @SuppressLint("RestrictedApi")
                @Override
                public void onFailure(Call<Data<Video>> call, Throwable t) {
                    Log.e(TAG, "回调失败:" + t.getMessage() + "," + t);
                    Toast.makeText(getContext(), "回调失败", Toast.LENGTH_SHORT).show();
                }
            });
        });
    }

初始化页面并调用网络请求
@Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initRequest();
        doNetwork(view);

    }

这几步代码是一点一点补全的,且均在同一个fragment类中


效果演示

点击前

点击后:

成功获取了网络资源!!!!!

3.retrofit与后端交互

流程与之前的get请求相似,故省略一些相同的步骤,此处的实体类Order请替换为自己的

在api接口里添加新方法
 @HTTP(method = "POST", path = "order/create",hasBody = true)
    Call<Data<Order>> createOrder(@Body Order order);

创建Retrofit实例
public void initRequest() {
        retrofit = new Retrofit.Builder()
                //设置网络请求BaseUrl地址
                .baseUrl("http://192.168.43.176:9000/")
                //设置数据解析器
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        //创建网络请求接口对象实例
        api = retrofit.create(Api.class);
    }

在学习阶段,baseurl请使用主机的IP地址而不是localhost,并且保持手机电脑处于同同一网络环境


创建网络请求接口
 @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //初始化请求
        initRequest();
        //初始化下单事件
        doCreateOrder(view);
    }

    /**
     * 下单按钮
     * @param view 下单页面
     */
    public void doCreateOrder(View view) {
        TextView buttonBack1 = view.findViewById(R.id.text_notifications);
        buttonBack1.setOnClickListener(view1 -> {
            Toast.makeText(getContext(), "点击了按钮!", Toast.LENGTH_SHORT).show();
            //对发送请求进行封装
            Call<Data<Order>> dataCall = api.createOrder(new Order(1, 1, 2, 114514));
            //处理请求数据
            dataCall.enqueue(new Callback<Data<Order>>() {
                @SuppressLint("SetTextI18n")
                @Override
                public void onResponse(Call<Data<Order>> call, Response<Data<Order>> response) {
                    Data<Order> body = response.body();
                    if (body == null) return;
                    Order order = body.getData();
                    if (order == null) return;
                    TextView tv_userId = view.findViewById(R.id.tv_order_userId);
                    tv_userId.setText(Integer.toString(order.getUserId()));
                    TextView tv_productId = view.findViewById(R.id.tv_order_productId);
                    tv_productId.setText(Integer.toString(order.getProductId()));
                    TextView money = view.findViewById(R.id.tv_order_money);
                    money.setText(Integer.toString(order.getMoney()));
                    TextView count = view.findViewById(R.id.tv_order_count);
                    count.setText(Integer.toString(order.getCount()));

                    Toast.makeText(getContext(), "创建订单成功:异步执行", Toast.LENGTH_SHORT).show();
                }

                @SuppressLint("RestrictedApi")
                @Override
                public void onFailure(Call<Data<Order>> call, Throwable t) {
                    Log.e(TAG, "回调失败:" + t.getMessage() + "," + t);
                    Toast.makeText(getContext(), "回调失败", Toast.LENGTH_SHORT).show();
                }
            });
        });
    }

创建后端
@RestController
@RequestMapping("/order")
public class OrderController {
    @Resource
    OrderService orderService;
    @Resource
    StorageFeign storageFeign;

    @Resource
    OrderTccService orderTccService;

    @PostMapping("/create")
    @GlobalTransactional
    public JSONObject orderAdd(@RequestBody Order order) {
        //orderService.save(order);
        orderTccService.create(order);
        storageFeign.deduction(order.getProductId(),order.getCount());
        Result<Order> json = new Result<>();
        json.put("code", 200);
        json.put("message", "请求数据成功");
        json.put("data", order);
    return json;
    }
}

此处为只粘贴了Controller部分,至于service及其相关的内容不属于本课程,不过多展示。

控制器接收一个order对象,但已经被前端@Body注解转化为json格式,后端完成服务后,向前端返回了该订单,

返回的而数据依然是一个json格式的对象,Result<T>与前端的Data<T>一样。

本地缓存数据库

添加依赖: implementation "androidx.room:room-runtime:2.4.0" , annotationProcessor "androidx.room:room-compiler:2.4.0"

添加对存储的读写权限:

<!--    读写手机存储权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
创建一个实体类Entity):实体类表示数据库中的表并定义表的结构可以使用@Entity注解标记类并使用@PrimaryKey注解标记主键字段还可以使用其他注解如@ColumnInfo@Ignore等来定义列信息和忽略字段
@Entity(tableName = "users")
public class User {
    @PrimaryKey
    public int id;

    @ColumnInfo(name = "name")
    public String name;
}

创建一个访问对象(DAO):DAO(Data Access Object)是用于访问数据库的接口或抽象类,定义了对数据库进行CRUD操作的方法。可以使用@Dao注解标记接口,并在方法上使用@Insert、@Update、@Delete等注解来指定对应的操作。

@Dao
public interface UserDao {
    @Insert
    void insert(User user);

    @Update
    void update(User user);

    @Delete
    void delete(User user);
}

创建数据库对象(Database):数据库对象是Room的核心部分,用于定义数据库的配置和操作方法。需要继承RoomDatabase类, 并使用@Database注解标记类,并指定包含的实体类和版本号。

由于数据库创建对资源消耗非常大,故使用单例模式来限制其声明。

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    private static volatile AppDatabase mAppDatabase;
    //单例设计模式
    public static AppDatabase getInstance(Context context) {
        if (mAppDatabase == null) {
            synchronized (AppDatabase.class) {
                if (mAppDatabase == null) {
                    mAppDatabase = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "dbRoomTest.db")
                            .addMigrations()
                            // 默认不允许在主线程中连接数据库
                            // .allowMainThreadQueries()
                            .build();
                }
            }
        }
        return mAppDatabase;
    }

    public abstract UserDao userDao();
}

初始化数据库:通常在Application类中进行数据库的初始化,通过调用Room.databaseBuilder()方法来创建数据库实例。

//全局初始化room
        AppDatabase db = AppDatabase.getInstance(this);
        UserDao userDao = db.userDao();
//执行数据库操作:通过获取数据库对象和DAO对象,可以进行数据库的操作,如插入数据、查询数据等。

        User user = new User();
        user.id = 1;
        user.name = "John";
        userDao().insert(user);
//这部分请使用新线程执行

这样就可以向Room数据库添加数据了。请注意,在主线程中执行数据库操作可能会导致UI卡顿,因此推荐使用异步操作,如使用AsyncTask、LiveData、RxJava等来处理数据库操作。

此外,还可以通过使用事务(Transaction)来执行一系列的数据库操作,保证数据的一致性和完整性。

以上是使用Room添加数据的基本流程,具体的实现可能会根据项目的需求和结构有所变化。建议参考Room的官方文档和示例代码,以了解更多关于Room数据库的使用方法。

使用Matisse实现拉取手机图片上传

引入依赖包:implementation 'com.zhihu.android:matisse:0.5.3-beta3'//动态权限implementation 'pub.devrel:easypermissions:2.0.1

编写图片选择器
public  void initImageSelected(){

        Matisse.from(this).choose(MimeType.ofImage(), false)//此处选择了图片文件
                .countable(true)
                .maxSelectable(1)     //最大选择数量
                .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))//过滤器
                .gridExpectedSize((int) getResources().getDimension(R.dimen.imageSelectDimen))
                .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
                .thumbnailScale(0.87f)     //清晰度,略缩图
                .imageEngine(new GlideEngine())  //加载图片引擎
                .forResult(REQUEST_CODE_CHOOSE);//设置请求码


    }
编写权限验证器
/**
     * 设置选择的图片数据,并做权限验证再进入相册
     */
    private void setPicData() {
        // 有权限,设置imageSelected
        if (EasyPermissions.hasPermissions(this,permissions)){
            initImageSelected();
        }else {
            // 无权限, 请求权限
            EasyPermissions.requestPermissions(this, "请求必要的权限,拒绝权限可能会无法使用!", REQUEST_CODE_CHOOSE, permissions);
        }
    }

既然都请求了权限,那当然有必要赋予app相关的权限,在清单文件中赋予权限:

<!--访问手机照片权限-->
<uses-permission android:name="android.permission.CAMERA" />

有了授权,就需要去确认是否授权成功,使用以下回调类,结果会自动返回setPicData

/**
     * 成功获取权限
     * @param requestCode  请求码
     * @param perms ?
     */
    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
        Log.e(TAG,perms.toString());
        initImageSelected();

    }
    /**
     * 获取权限失败
     * @param requestCode  请求码
     * @param perms ?
     */
    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        Log.e(TAG,"权限请求失败");
        //拒绝权限且点击了不再提示
        if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
            //跳转应用设置页面
            new AppSettingsDialog.Builder(this).build().show();
        } else {
            EasyPermissions.requestPermissions(this, "请求必要的权限,拒绝权限可能会无法使用!", 1, perms.toString());
        }
    }
设置图片接收器

效果类似于下级向上级传递参数:

  /**
     * 接收matisse返回的图片
     * @param requestCode  请求码
     * @param resultCode  返回码
     * @param data   返回的图片
     */
    @SuppressLint("CheckResult")
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        CircleImageView imageView = findViewById(R.id.profile_avatar_view);
        if (requestCode == REQUEST_CODE_CHOOSE && resultCode == RESULT_OK) {
            //图片路径 同样视频地址也是这个
            List<String> pathList = Matisse.obtainPathResult(data);
            //Uri 格式的
            List<Uri> pathList1 = Matisse.obtainResult(data);

            for (int i = 0; i < pathList.size(); i++) {
                Log.i("图片" + (i + 1) + "地址", pathList.get(i));
                uploadFile(pathList.get(i));  //自定义的文件上传方式
            }
            uri = pathList1.get(0);
            if (uri != null) { //将图片跟新到头像当中
                Glide.with(ProfileActivity.this)
                        .asBitmap() // some .jpeg files are actually gif
                        .load(uri)
                        .apply(new RequestOptions() {{
                            override(Target.SIZE_ORIGINAL);
                        }})
                        .into(imageView);
            }
            else {
                Toast.makeText(ProfileActivity.this, "没找到图片", Toast.LENGTH_SHORT).show();
            }
        }
    }

自此,Matisse打开相册功能示例结束。

视频播放功能

创建播放布局
<VideoView
            android:id="@+id/video_player"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:foregroundGravity="center" />
初始化视频数据

1、首先声明两个对象:视频播放器和播放媒体控制器

//视频播放器
private VideoView video_player;
//播放媒体控制器
private MediaController mMediaController;

2、在后端返回数据的地方填入视频地址,sorts是后端返回的视频列表

sortAdapter1.setList(sorts);
        sortAdapter1.setOnItemClickListener((sortAdapter, view, position) -> {
        String uri = videos.getVideo().get(position).getVideoUrl();
        //String uri2 = "http://192.168.43.176:9200/file/videoFile/摇曳露营-0.mp4";  //网络
        video_player.setVideoURI(Uri.parse(uri));  //设置网络视频的地址
        mMediaController.setMediaPlayer(video_player);// 为控制器指定播放器
        mMediaController.setVisibility(View.GONE);//我设置了控制器隐藏,其本身ui略丑
        video_player.setMediaController(mMediaController);// 为播放器指定控制器
        video_player.start();
        });

下拉刷新功能

添加依赖:implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"

1、创建列表布局

SwipeRefreshLayout内只能有一个子控件并且必须具有滑动功能。

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/msg_swipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="70dp"
        android:layout_weight="1">


        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent" />


    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
2、实现下拉时的操作
private void handleDownPullUpdate() {
        //设置刷新时小圆圈的颜色变化
        swipeRefreshLayout.setEnabled(true);
        swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary, R.color.colorSky);
        swipeRefreshLayout.setOnRefreshListener(() -> {
            //被刷新时的操作
            //更新UI
            loadMessageData(user.getId());
            new Handler().postDelayed(() -> {
                //更新成功后设置UI,停止更新
                Toast.makeText(getContext(), "刷新成功!", Toast.LENGTH_SHORT).show();
                swipeRefreshLayout.setRefreshing(false);
            }, 2000);//理论时间应该是等待请求完成
        });
    }
木兰宽松许可证, 第2版 木兰宽松许可证, 第2版 2020年1月 http://license.coscl.org.cn/MulanPSL2 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 0. 定义 “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 “法人实体”是指提交贡献的机构及其“关联实体”。 “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 1. 授予版权许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 2. 授予专利许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 3. 无商标许可 “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 4. 分发限制 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 5. 免责声明与责任限制 “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 6. 语言 “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 条款结束 如何将木兰宽松许可证,第2版,应用到您的软件 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 3, 请将如下声明文本放入每个源文件的头部注释中。 Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. Mulan Permissive Software License,Version 2 Mulan Permissive Software License,Version 2 (Mulan PSL v2) January 2020 http://license.coscl.org.cn/MulanPSL2 Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 0. Definition Software means the program and related documents which are licensed under this License and comprise all Contribution(s). Contribution means the copyrightable work licensed by a particular Contributor under this License. Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. Legal Entity means the entity making a Contribution and all its Affiliates. Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 1. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 2. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 3. No Trademark License No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 4. Distribution Restriction You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 5. Disclaimer of Warranty and Limitation of Liability THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 6. Language THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. END OF THE TERMS AND CONDITIONS How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.

简介

主要针对二次元人群的需求,设计且包含了基于Live2d+VITS+chatGPT的互动聊天模型,二次元视频创作交流平台(可参考哔哩哔哩),仅供学习和复习。 展开 收起
Java
MulanPSL-2.0
取消

发行版 (2)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/xiyv/NEKOLINE.git
git@gitee.com:xiyv/NEKOLINE.git
xiyv
NEKOLINE
NEKOLINE
master

搜索帮助