android项目 |《视频资讯应用程序》| 避错 注释 兼容 完整(一)| 无知的我复盘日记(图文排版无水印)

无知的我接触到了Android。

特点是 将会解决后端数据问题、解决数据库版本等等问题。

对于完整的笔记,我已经在本地完成,记录了从开始学习到制作完成的详细过程。但是如果没有人需要,后续不会再更新了。

目录

UI界面DIY

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

环境搭建

1 工具

​ 1.1 使用Adobe xd设计原型图

​ 1.2 使用PxCook生成设计安卓代码

​ 1.3 PxCook需要基于安装Adobe air

2 创建项目

3 选择模拟器型号

选择苹果尺寸和苹果分辨率

在这里插入图片描述
4 下载Api(系统镜像)

在这里插入图片描述

颜色配置文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#6200EE</color>
    <color name="colorPrimaryDark">#3700B3</color>
    <color name="colorAccent">#03DAC5</color>
    <color name="white">#ffffff</color>
    <color name="black">#000000</color>
    <color name="dividser">#e8e7e7</color>
    <color name="skin_text_white">#ffffff</color>
    <color name="skin_topbar_bg_color">#344261</color>
</resources>

尺寸配置文件

位置:values文件夹下,创建dimens.xml文件

所有尺寸的配置都在此配置

在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="dimen_90dp">90dp</dimen>
    <dimen name="dimen_44dp">44dp</dimen>
    <dimen name="size_20sp">20sp</dimen>
</resources>

启动页面UI

布局文件

位置:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@mipmap/splash"
    tools:context=".MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="@dimen/dimen_90dp"
        android:paddingLeft="@dimen/dimen_44dp"
        android:paddingRight="@dimen/dimen_44dp">

        <Button
            android:id="@+id/btn_login"
            android:layout_width="100dp"
            android:layout_height="40dp"
            android:layout_alignParentLeft="true"
            android:text="@string/login"
            android:textColor="#ffffff"
            android:background="@drawable/shape_login_btn"
            android:textSize="@dimen/size_20sp" />

        <Button
            android:id="@+id/btn_register"
            android:layout_width="100dp"
            android:layout_height="40dp"
            android:background="@drawable/shape_register_btn"
            android:layout_alignParentRight="true"
            android:text="@string/register"
            android:textColor="#ffffff"
            android:textSize="20sp" />
    </RelativeLayout>

</RelativeLayout>

还需:1.编写values相关配置 2.引入相关资源文件,其中背景图存放到mipmap-xxxhdpi文件下

矩形按钮

位置:drawble文件夹,创建xml资源文件:1.根元素为shape的;2.名字为shape_login_btn

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
<!--    @rectangle:圆角矩形-->
    <corners android:radius="9dp" />
    <solid android:color="#c75fe768" />
</shape>

在这里插入图片描述

无状态条

位置:AndroidManifest.xml,修改

        android:theme="@style/Theme.AppCompat.NoActionBar">

运行图片

在这里插入图片描述

登录页面UI

创建activity文件夹,其中空活动:LoginActivity

布局文件

位置:activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    tools:context=".activity.LoginActivity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="329dp"
        android:scaleType="fitXY"
        android:src="@mipmap/login" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="278dp"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="230dp"
            android:layout_marginLeft="18dp"
            android:layout_marginRight="18dp"
            android:background="@drawable/shape_login_form"
            android:gravity="center"
            android:orientation="vertical"
            android:paddingLeft="43dp"
            android:paddingRight="31dp">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal">

                <ImageView
                    android:layout_width="25dp"
                    android:layout_height="25dp"
                    android:src="@mipmap/account" />

                <EditText
                    android:id="@+id/et_account"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="20dp"
                    android:background="@null"
                    android:hint="@string/account_hint"
                    android:textColor="#000000"
                    android:text="root"
                    android:textColorHint="#bcbcbc"
                    android:textSize="18sp" />
            </LinearLayout>

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginTop="23dp"
                android:layout_marginBottom="23dp"
                android:background="#e8e7e7" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal">

                <ImageView
                    android:layout_width="25dp"
                    android:layout_height="25dp"
                    android:src="@mipmap/pwd" />

                <EditText
                    android:id="@+id/et_pwd"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="20dp"
                    android:background="@null"
                    android:text="123456"
                    android:inputType="textPassword"
                    android:hint="@string/pwd_hint"
                    android:textColor="#000000"
                    android:textColorHint="#bcbcbc"
                    android:textSize="18sp" />
            </LinearLayout>

        </LinearLayout>

        <Button
            android:id="@+id/btn_login"
            android:layout_width="match_parent"
            android:layout_height="55dp"
            android:layout_marginLeft="18dp"
            android:layout_marginTop="67dp"
            android:layout_marginRight="18dp"
            android:background="@drawable/shape_big_login_btn"
            android:text="@string/login"
            android:textColor="#ffffff"
            android:textSize="24sp" />
    </LinearLayout>
</RelativeLayout>

阴影边框

在shape_login_form.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 阴影边 -->
    <item>
        <shape android:shape="rectangle">
            <padding
                android:bottom="2dp"
                android:left="1.5dp"
                android:right="2dp"
                android:top="1.5dp" />
            <solid android:color="#F2F2F2" />
            <corners android:radius="8dp" />
        </shape>
    </item>
    <!-- 中心白色背景 -->
    <item>
        <shape
            android:shape="rectangle"
            android:useLevel="false">
            <!-- 实心 -->
            <solid android:color="#ffffff" />
            <corners android:radius="10dp" />
            <padding
                android:bottom="10dp"
                android:left="10dp"
                android:right="10dp"
                android:top="10dp" />
        </shape>
    </item>
</layer-list>

在这里插入图片描述

// 原理:两个卡片叠加实现

运行图片

在这里插入图片描述

注册页面

在activity文件夹下创建空activity,名字:RegisterActivity

//便捷:复制登录页面,修改即可

布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    tools:context=".activity.LoginActivity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="329dp"
        android:scaleType="fitXY"
        android:src="@mipmap/login" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="278dp"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="230dp"
            android:layout_marginLeft="18dp"
            android:layout_marginRight="18dp"
            android:background="@drawable/shape_login_form"
            android:gravity="center"
            android:orientation="vertical"
            android:paddingLeft="43dp"
            android:paddingRight="31dp">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal">

                <ImageView
                    android:layout_width="25dp"
                    android:layout_height="25dp"
                    android:src="@mipmap/account" />

                <EditText
                    android:id="@+id/et_account"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="20dp"
                    android:background="@null"
                    android:hint="@string/account_hint"
                    android:textColor="#000000"
                    android:textColorHint="#bcbcbc"
                    android:textSize="18sp" />
            </LinearLayout>

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginTop="23dp"
                android:layout_marginBottom="23dp"
                android:background="@color/divider" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal">

                <ImageView
                    android:layout_width="25dp"
                    android:layout_height="25dp"
                    android:src="@mipmap/pwd" />

                <EditText
                    android:id="@+id/et_pwd"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="20dp"
                    android:background="@null"
                    android:inputType="textPassword"
                    android:hint="@string/pwd_hint"
                    android:textColor="@color/black"
                    android:textColorHint="#bcbcbc"
                    android:textSize="18sp" />
            </LinearLayout>

        </LinearLayout>

        <Button
            android:id="@id/btn_register"
            android:layout_width="match_parent"
            android:layout_height="55dp"
            android:layout_marginLeft="18dp"
            android:layout_marginTop="67dp"
            android:layout_marginRight="18dp"
            android:background="@drawable/shape_big_register_btn"
            android:text="@string/register"
            android:textColor="@color/white"
            android:textSize="24sp" />
    </LinearLayout>
</RelativeLayout>

运行图片

在这里插入图片描述

启动页面跳转

package com.ttit.myapp;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.ttit.myapp.activity.BaseActivity;
import com.ttit.myapp.activity.LoginActivity;
import com.ttit.myapp.activity.RegisterActivity;

public class MainActivity extends BaseActivity {
   private Button btnLogin;
   private Button btnRegister;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       //登录按钮
       btnLogin = findViewById(R.id.btn_login);
       btnLogin.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
//                Intent intent = new Intent(MainActivity.this, LoginActivity.class);
//                startActivity(intent);
               navigateTo(LoginActivity.class);
           }
       });
       //注册按钮
       btnRegister = findViewById(R.id.btn_register);
       btnRegister.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
//                Intent intent = new Intent(MainActivity.this, RegisterActivity.class);
//                startActivity(intent);
               navigateTo(RegisterActivity.class);
           }
       });
   }
}

跳转的封装方法

   //活动跳转
    public void navigateTo(Class cls) {
        Intent in = new Intent(mContext, cls);
        startActivity(in);
    }

登录逻辑

易错改正:记得继承类

登录验证

package com.ttit.myapp.activity;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.ttit.myapp.R;
import com.ttit.myapp.util.StringUtils;

public class LoginActivity extends BaseActivity {

    private Button btnLogin;
    private EditText etAccount;
    private EditText etPwd;
    @Override
    protected void onCreate(Bundle   savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        //登录验证
        etAccount = findViewById(R.id.et_account);
        etPwd = findViewById(R.id.et_pwd);
        btnLogin = findViewById(R.id.btn_login);
        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String account = etAccount.getText().toString().trim();
                String pwd = etPwd.getText().toString().trim();
                login(account, pwd);
            }
        });
    }

    private void login(String account, String pwd) {
        if (StringUtils.isEmpty(account)) {
            showToast("请输入账号");
            return;
        }
        if (StringUtils.isEmpty(pwd)) {
            showToast("请输入密码");
            return;
        }
    }
}

活动工具类

1.编写活动跳转方法

2.编写提示信息方法

package com.ttit.myapp.activity;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public abstract class BaseActivity extends AppCompatActivity {
    public Context mContext;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
    }
    //提示信息
    public void showToast(String msg) {
        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
    }
    //活动跳转
    public void navigateTo(Class cls) {
        Intent in = new Intent(mContext, cls);
        startActivity(in);
    }
}

字符串是否为空

创建工具类

package com.ttit.myapp.util;

public class StringUtils {
    public static boolean isEmpty(String str) {
        if (str == null || str.length() <= 0) {
            return true;
        } else {
            return false;
        }
    }
}

测试运行

是否跳转成功以及验证是否成功

登录接口

1 导入依赖

在这里插入图片描述

// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.10.0'

//网址:https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp/3.10.0

简写版:

implementation 'com.squareup.okhttp3:okhttp:3.10.0'

//点击synoc now 效果:开始下载依赖

2 打开网络权限

在这里插入图片描述

3 使用okhttp

  1. 请求数据
  2. 请求类型(请求头设置)
  3. 请求编码
  4. 请求地址
  5. 发送请求
  6. 请求结果处理

1 请求代码

private void login(String account, String pwd) {
    if (StringUtils.isEmpty(account)) {
        showToast("请输入账号");
        return;
    }
    if (StringUtils.isEmpty(pwd)) {
        showToast("请输入密码");
        return;
    }
    //    第一步创建OKHttpClient
    OkHttpClient client = new OkHttpClient.Builder()
        .build();
    Map m = new HashMap();
    m.put("mobile", account);
    m.put("password", pwd);
    JSONObject jsonObject = new JSONObject(m);
    String jsonStr = jsonObject.toString();
    RequestBody requestBodyJson =
        RequestBody.create(MediaType.parse("application/json;charset=utf-8")
                           , jsonStr);
    //第三步创建Rquest
    Request request = new Request.Builder()
        .url(AppConfig.BASE_URl + "/app/login")
        .addHeader("contentType", "application/json;charset=UTF-8")
        .post(requestBodyJson)
        .build();
    //第四步创建call回调对象
    final Call call = client.newCall(request);
    //第五步发起请求
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            Log.e("onFailure", e.getMessage());
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            final String result = response.body().string();
            //ui必须在主线程处理 //原因:若在子线程处理会报运行时错误
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    showToast(result);
                }
            });
        }
    });
}

2 在其中,需要封装请求的基础地址:

package com.ttit.myapp.util;

/**
 *@author wuzhideren
 */
public class AppConfig {
    public static final String BASE_URl = "http://192.168.31.32:8080/renren-fast";
}

3 需要取消只能发送https的限制:

1 创建.xml文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4MCuz2Y0-1643208228100)(视频资讯app.assets/image-20220117191714496.png)]

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <!--禁用掉明文流量请求的检查-->
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

2 在核心配置文件中引入该.xml文件:

在这里插入图片描述

// 原因:在27版本以上默认只能发送https请求

4 测试运行

注册接口

注册逻辑类

package com.ttit.myapp.activity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.ttit.myapp.R;
import com.ttit.myapp.api.Api;
import com.ttit.myapp.api.ApiConfig;
import com.ttit.myapp.api.TtitCallback;
import com.ttit.myapp.util.StringUtils;

import java.util.HashMap;

public class RegisterActivity extends BaseActivity {

    private Button btnRegister;
    private EditText etAccount;
    private EditText etPwd;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);

        //登录验证
        etAccount = findViewById(R.id.et_account);
        etPwd = findViewById(R.id.et_pwd);
        btnRegister = findViewById(R.id.btn_register);
        btnRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String account = etAccount.getText().toString().trim();
                String pwd = etPwd.getText().toString().trim();
                register(account, pwd);
            }
        });
    }
    private void register(String account, String pwd) {
        if (StringUtils.isEmpty(account)) {
            showToast("请输入账号");
            return;
        }
        if (StringUtils.isEmpty(pwd)) {
            showToast("请输入密码");
            return;
        }
        HashMap<String, Object> params = new HashMap<String, Object>();
        params.put("mobile", account);
        params.put("password", pwd);
        Api.config(ApiConfig.REGISTER, params).postRequest(this,new TtitCallback() {
            @Override
            public void onSuccess(final String res) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        showToast(res);
                    }
                });
            }

            @Override
            public void onFailure(Exception e) {
                Log.e("onFailure", e.toString());
            }
        });
    }
}

子线程方法

//处理子线程ui
public void showToastSync(String msg) {
    //令子线程可以有ui显示,而不报错
    Looper.prepare();
    Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
    Looper.loop();
}

//作用:处理子线程ui

测试运行

本地登录信息

登录实体类

package com.ttit.myapp.entity;

public class LoginResponse {

    /**
     * msg : success
     * code : 0
     * expire : 604800
     * token : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI2IiwiaWF0IjoxNTkyNDg2OTQzLCJleHAiOjE1OTMwOTE3NDN9.f5sxyG60GyDlj0FcZEmPAADiLHX_pATrvicxbADqvRqYurYQC5s0KAjw5XgHS4gpk-qUSwWtcJpY_nJjYf_2Dw
     */

    private String msg;
    private int code;
    private int expire;
    private String token;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

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

    public int getExpire() {
        return expire;
    }

    public void setExpire(int expire) {
        this.expire = expire;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }
}

//扩展-快速生成方法:使用GsonFormat插件,把json格式字符串快速转化实体类。

Gson依赖

implementation 'com.google.code.gson:gson:2.8.5'

api文件夹

TtitCallback

package com.ttit.myapp.api;

public interface TtitCallback {

    void onSuccess(String res);

    void onFailure(Exception e);
}

ApiConfig

package com.ttit.myapp.api;

public class ApiConfig {
    public static final String BASE_URl = "http://192.168.31.32:8080/renren-fast";
    public static final String LOGIN = "/app/login"; //登录
    public static final String REGISTER = "/app/register";//注册
}

Api

package com.ttit.myapp.api;

import static android.content.Context.MODE_PRIVATE;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;

import com.ttit.myapp.activity.LoginActivity;
import com.ttit.myapp.util.StringUtils;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class Api {
    private static OkHttpClient client;
    private static String requestUrl;
    private static HashMap<String, Object> mParams;
    public static Api api = new Api();

    public Api() {

    }

    public static Api config(String url, HashMap<String, Object> params) {
        client = new OkHttpClient.Builder()
                .build();
        requestUrl = ApiConfig.BASE_URl + url;
        mParams = params;
        return api;
    }

    public void postRequest(Context context, final TtitCallback callback) {
        SharedPreferences sp = context.getSharedPreferences("sp_ttit", MODE_PRIVATE);
        String token = sp.getString("token", "");
        JSONObject jsonObject = new JSONObject(mParams);
        String jsonStr = jsonObject.toString();
        RequestBody requestBodyJson =
                RequestBody.create(MediaType.parse("application/json;charset=utf-8")
                        , jsonStr);
        //第三步创建Rquest
        Request request = new Request.Builder()
                .url(requestUrl)
                .addHeader("contentType", "application/json;charset=UTF-8")
                .addHeader("token", token)
                .post(requestBodyJson)
                .build();
        //第四步创建call回调对象
        final Call call = client.newCall(request);
        //第五步发起请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("onFailure", e.getMessage());
                callback.onFailure(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String result = response.body().string();
                callback.onSuccess(result);
            }
        });
    }
}

存储逻辑类

位置:在activity文件夹下,LoginActivity.class

private void login(String account, String pwd) {
        if (StringUtils.isEmpty(account)) {
            showToast("请输入账号");
            return;
        }
        if (StringUtils.isEmpty(pwd)) {
            showToast("请输入密码");
            return;
        }
        HashMap<String, Object> params = new HashMap<String, Object>();
        params.put("mobile", account);
        params.put("password", pwd);
        Api.config(ApiConfig.LOGIN, params).postRequest(this,new TtitCallback() {
            @Override
            public void onSuccess(final String res) {
                Log.e("onSuccess", res);
                //json格式字符串转化为类对象
                Gson gson = new Gson();
                LoginResponse loginResponse = gson.fromJson(res, LoginResponse.class);
                if (loginResponse.getCode() == 0) {
                    String token = loginResponse.getToken();
                    insertVal("token", token);
                    showToastSync("登录成功");
                } else {
                    showToastSync("登录失败");
                }
            }

            @Override
            public void onFailure(Exception e) {

            }
        });
    }

SP存储方法

    //处理子线程ui
    public void showToastSync(String msg) {
        //令子线程可以有ui显示,而不报错
        Looper.prepare();
        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
        Looper.loop();//易错修正:其后代码不会立即执行
    }

测试运行

主页实现

跳转到主页:

navigateTo(HomeActivity.class);//若写在83行,则因为线程原因不会执行该代码。

底部tab栏

导入依赖

implementation 'com.flyco.tablayout:FlycoTabLayout_Lib:2.1.2@aar'

// 目的:为了使用CommonTabLayout控件

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tl="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="#ffffff"
    android:orientation="vertical"
    tools:context=".activity.HomeActivity">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/divider" />

    <com.flyco.tablayout.CommonTabLayout
        android:id="@+id/commonTabLayout"
        android:layout_width="match_parent"
        android:layout_height="66dp"
        android:background="#ffffff"
        tl:tl_iconHeight="30dp"
        tl:tl_iconWidth="30dp"
        tl:tl_indicator_color="#2C97DE"
        tl:tl_indicator_height="0dp"
        tl:tl_textSelectColor="#0025ff"
        tl:tl_textUnselectColor="#454544"
        tl:tl_textsize="14sp"
        tl:tl_underline_color="#DDDDDD"
        tl:tl_underline_height="1dp" />
<!--该控件为继承frag的自定义控件
@tl_textSelectColor 被选中时,的颜色-->
</LinearLayout>

易错改正:不能使用viewpager2

基本框架

HomeActivity

package com.ttit.myapp.activity;

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

import android.os.Bundle;

import com.flyco.tablayout.CommonTabLayout;
import com.flyco.tablayout.listener.CustomTabEntity;
import com.ttit.myapp.R;

import java.util.ArrayList;

public class HomeActivity extends AppCompatActivity {
    //导航栏字符串
    private String[] mTitles = {"首页", "资讯", "我的"};
    //导航栏图片
    private int[] mIconUnselectIds = {
            R.mipmap.home_unselect, R.mipmap.collect_unselect,
            R.mipmap.my_unselect};
    private int[] mIconSelectIds = {
            R.mipmap.home_selected, R.mipmap.collect_selected,
            R.mipmap.my_selected};
    //装入导航栏分页的页面,就是fragment
    private ArrayList<Fragment> mFragments = new ArrayList<>();
    private ArrayList<CustomTabEntity> mTabEntities = new ArrayList<>();
    //这是分页
    private ViewPager viewPager;
    private CommonTabLayout commonTabLayout;

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

fragment

新建三个基本fragment,并且改传参实例化方法为无参的

image-20220117230657930

其中之一:

package com.ttit.myapp.fragment;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.fragment.app.Fragment;

import com.ttit.myapp.R;

/**
 * A simple {@link Fragment} subclass.
 * Use the {@link CollectFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class CollectFragment extends Fragment {

    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;

    public CollectFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment CollectFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static CollectFragment newInstance() {
        CollectFragment fragment = new CollectFragment();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_collect, container, false);
    }
}

adapter

新建、继承、配置

package com.ttit.myapp.adapter;

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

import java.util.ArrayList;
//用于设置pager的页面
public class MyPagerAdapter extends FragmentPagerAdapter {

    private String[] mTitles;
    private ArrayList<Fragment> mFragments;
    //通过构造器传入页面属性,以设置
    public MyPagerAdapter(FragmentManager fm, String[] titles, ArrayList<Fragment> fragments) {
        super(fm);
        this.mTitles = titles;
        this.mFragments = fragments;
    }

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

    @Override
    public CharSequence getPageTitle(int position) {
        return mTitles[position];
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }
}

基本配置

TabEntity

package com.ttit.myapp.entity;

import com.flyco.tablayout.listener.CustomTabEntity;

public class TabEntity implements CustomTabEntity {
    public String title;
    public int selectedIcon;
    public int unSelectedIcon;

    public TabEntity(String title, int selectedIcon, int unSelectedIcon) {
        this.title = title;
        this.selectedIcon = selectedIcon;
        this.unSelectedIcon = unSelectedIcon;
    }

    @Override
    public String getTabTitle() {
        return title;
    }

    @Override
    public int getTabSelectedIcon() {
        return selectedIcon;
    }

    @Override
    public int getTabUnselectedIcon() {
        return unSelectedIcon;
    }
}

HomeActivity

package com.ttit.myapp.activity;

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

import android.os.Bundle;

import com.flyco.tablayout.CommonTabLayout;
import com.flyco.tablayout.listener.CustomTabEntity;
import com.flyco.tablayout.listener.OnTabSelectListener;
import com.ttit.myapp.R;
import com.ttit.myapp.adapter.MyPagerAdapter;
import com.ttit.myapp.entity.TabEntity;
import com.ttit.myapp.fragment.CollectFragment;
import com.ttit.myapp.fragment.HomeFragment;
import com.ttit.myapp.fragment.MyFragment;

import java.util.ArrayList;

public class HomeActivity extends AppCompatActivity {
    //导航栏字符串
    private String[] mTitles = {"首页", "资讯", "我的"};
    //导航栏图片
    private int[] mIconUnselectIds = {
            R.mipmap.home_unselect, R.mipmap.collect_unselect,
            R.mipmap.my_unselect};
    private int[] mIconSelectIds = {
            R.mipmap.home_selected, R.mipmap.collect_selected,
            R.mipmap.my_selected};
    //装入导航栏分页的页面,就是fragment
    private ArrayList<Fragment> mFragments = new ArrayList<>();
    private ArrayList<CustomTabEntity> mTabEntities = new ArrayList<>();
    //这是分页
    private ViewPager viewPager;
    //这是四个按钮
    private CommonTabLayout commonTabLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        viewPager = findViewById(R.id.viewpager);
        commonTabLayout = findViewById(R.id.commonTabLayout);
        //添加fra实例化对象入集合
        mFragments.add(HomeFragment.newInstance());
        mFragments.add(CollectFragment.newInstance());
        mFragments.add(MyFragment.newInstance());
        //添加tab实例化对象入集合
        for (int i = 0; i < mTitles.length; i++) {
            mTabEntities.add(new TabEntity(mTitles[i], mIconSelectIds[i], mIconUnselectIds[i]));
        }
        //实现点击tab,页面切换
        commonTabLayout.setOnTabSelectListener(new OnTabSelectListener() {
            //@position frag中集合的下标
            @Override
            public void onTabSelect(int position) {
                //设置分页的当前页
                viewPager.setCurrentItem(position);
            }

            @Override
            public void onTabReselect(int position) {
            }
        });
        //为tab视图对象,设置tab集合
        commonTabLayout.setTabData(mTabEntities);
        //为pager添加上述内容
        viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager(), mTitles, mFragments));
    }
}

测试运行

image-20220118132624839

Tab栏优化

导航栏切换

//导航栏对应切换
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    //当页面切换时,传入当前页面坐标,让导航栏对应地切换
    @Override
    public void onPageSelected(int position) {
        commonTabLayout.setCurrentTab(position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

取消点击滑动

1 新建自定义FixedViewPager

2 继承ViewPager

3 修改public void setCurrentItem(int item){}内调用的父方法参数smoothScrollfalse

package com.ttit.myapp.view;

import android.content.Context;
import android.util.AttributeSet;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;

public class FixedViewPager extends ViewPager {
    public FixedViewPager(@NonNull Context context) {
        super(context);
    }

    public FixedViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setCurrentItem(int item) {
        super.setCurrentItem(item, false);
    }
}

修改布局文件的控件为自定义

<com.ttit.myapp.view.FixedViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

优化代码

    //基本代码封装
    protected abstract int initLayout();

    protected abstract void initView();

    protected abstract void initData();

BaseActivity

package com.ttit.myapp.activity;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Looper;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public abstract class BaseActivity extends AppCompatActivity {
    public Context mContext;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        //调用封装方法,传入各实现类的参数
        setContentView(initLayout());
        initView();
        initData();
    }
    //基本代码封装
    protected abstract int initLayout();

    protected abstract void initView();

    protected abstract void initData();
    //提示信息
    public void showToast(String msg) {
        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
    }

    //处理子线程ui
    public void showToastSync(String msg) {
        //令子线程可以有ui显示,而不报错
        Looper.prepare();
        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
        Looper.loop();//易错修正:其后代码不会立即执行
    }

    //活动跳转
    public void navigateTo(Class cls) {
        Intent in = new Intent(mContext, cls);
        startActivity(in);
    }

    //sp存储
    protected void insertVal(String key, String val) {
        SharedPreferences sp = getSharedPreferences("sp_ttit", MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        editor.putString(key, val);
        editor.commit();
    }
}

HomeActivity

package com.ttit.myapp.activity;

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

import android.os.Bundle;

import com.flyco.tablayout.CommonTabLayout;
import com.flyco.tablayout.listener.CustomTabEntity;
import com.flyco.tablayout.listener.OnTabSelectListener;
import com.ttit.myapp.R;
import com.ttit.myapp.adapter.MyPagerAdapter;
import com.ttit.myapp.entity.TabEntity;
import com.ttit.myapp.fragment.CollectFragment;
import com.ttit.myapp.fragment.HomeFragment;
import com.ttit.myapp.fragment.MyFragment;

import java.util.ArrayList;

public class HomeActivity extends BaseActivity {
    //导航栏字符串
    private String[] mTitles = {"首页", "资讯", "我的"};
    //导航栏图片
    private int[] mIconUnselectIds = {
            R.mipmap.home_unselect, R.mipmap.collect_unselect,
            R.mipmap.my_unselect};
    private int[] mIconSelectIds = {
            R.mipmap.home_selected, R.mipmap.collect_selected,
            R.mipmap.my_selected};
    //装入导航栏分页的页面,就是fragment
    private ArrayList<Fragment> mFragments = new ArrayList<>();
    private ArrayList<CustomTabEntity> mTabEntities = new ArrayList<>();
    //这是分页
    private ViewPager viewPager;
    //这是四个按钮
    private CommonTabLayout commonTabLayout;

    @Override
    protected int initLayout() {
        return R.layout.activity_home;
    }

    @Override
    protected void initView() {
        viewPager = findViewById(R.id.viewpager);
        commonTabLayout = findViewById(R.id.commonTabLayout);
    }

    @Override
    protected void initData() {
        //添加fra实例化对象入集合
        mFragments.add(HomeFragment.newInstance());
        mFragments.add(CollectFragment.newInstance());
        mFragments.add(MyFragment.newInstance());
        //添加tab实例化对象入集合
        for (int i = 0; i < mTitles.length; i++) {
            mTabEntities.add(new TabEntity(mTitles[i], mIconSelectIds[i], mIconUnselectIds[i]));
        }
        //实现点击tab,页面切换
        commonTabLayout.setOnTabSelectListener(new OnTabSelectListener() {
            //@position frag中集合的下标
            @Override
            public void onTabSelect(int position) {
                //设置分页的当前页
                viewPager.setCurrentItem(position);
            }

            @Override
            public void onTabReselect(int position) {
            }
        });
        //为tab视图对象,设置tab集合
        commonTabLayout.setTabData(mTabEntities);
        //为pager添加上述内容
        viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager(), mTitles, mFragments));
        //导航栏对应切换
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            //当页面切换时,传入当前页面坐标,让导航栏对应地切换
            @Override
            public void onPageSelected(int position) {
                commonTabLayout.setCurrentTab(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }
}

LoginActivity

package com.ttit.myapp.activity;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.google.gson.Gson;
import com.ttit.myapp.R;
import com.ttit.myapp.api.Api;
import com.ttit.myapp.api.ApiConfig;
import com.ttit.myapp.api.TtitCallback;
import com.ttit.myapp.entity.LoginResponse;
import com.ttit.myapp.util.AppConfig;
import com.ttit.myapp.util.StringUtils;

import org.json.JSONObject;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class LoginActivity extends BaseActivity {

    private Button btnLogin;
    private EditText etAccount;
    private EditText etPwd;

    @Override
    protected int initLayout() {
        return R.layout.activity_login;
    }

    @Override
    protected void initView() {
        etAccount = findViewById(R.id.et_account);
        etPwd = findViewById(R.id.et_pwd);
        btnLogin = findViewById(R.id.btn_login);
    }

    @Override
    protected void initData() {
        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String account = etAccount.getText().toString().trim();
                String pwd = etPwd.getText().toString().trim();
                login(account, pwd);
            }
        });
    }
    //登录验证
    private void login(String account, String pwd) {
        if (StringUtils.isEmpty(account)) {
            showToast("请输入账号");
            return;
        }
        if (StringUtils.isEmpty(pwd)) {
            showToast("请输入密码");
            return;
        }
        HashMap<String, Object> params = new HashMap<String, Object>();
        params.put("mobile", account);
        params.put("password", pwd);
        Api.config(ApiConfig.LOGIN, params).postRequest(this,new TtitCallback() {
            @Override
            public void onSuccess(final String res) {
                Log.e("onSuccess", res);
                //json格式字符串转化为类对象
                Gson gson = new Gson();
                LoginResponse loginResponse = gson.fromJson(res, LoginResponse.class);
                if (loginResponse.getCode() == 0) {
                    String token = loginResponse.getToken();
                    insertVal("token", token);
                    navigateTo(HomeActivity.class);//若写在83行,则因为线程原因不会执行该代码。
                    showToastSync("登录成功");
                } else {
                    showToastSync("登录失败");
                }
            }

            @Override
            public void onFailure(Exception e) {
                navigateTo(HomeActivity.class);
            }
        });
    }

//    private void login(String account, String pwd) {
//        if (StringUtils.isEmpty(account)) {
//            showToast("请输入账号");
//            return;
//        }
//        if (StringUtils.isEmpty(pwd)) {
//            showToast("请输入密码");
//            return;
//        }
//        //    第一步创建OKHttpClient
//        OkHttpClient client = new OkHttpClient.Builder()
//                .build();
//        Map m = new HashMap();
//        m.put("mobile", account);
//        m.put("password", pwd);
//        JSONObject jsonObject = new JSONObject(m);
//        String jsonStr = jsonObject.toString();
//        RequestBody requestBodyJson =
//                RequestBody.create(MediaType.parse("application/json;charset=utf-8")
//                        , jsonStr);
//        //第三步创建Rquest
//        Request request = new Request.Builder()
//                .url(AppConfig.BASE_URl + "/app/login")
//                .addHeader("contentType", "application/json;charset=UTF-8")
//                .post(requestBodyJson)
//                .build();
//        //第四步创建call回调对象
//        final Call call = client.newCall(request);
//        //第五步发起请求
//        call.enqueue(new Callback() {
//            @Override
//            public void onFailure(Call call, IOException e) {
//                Log.e("onFailure", e.getMessage());
//            }
//
//            @Override
//            public void onResponse(Call call, Response response) throws IOException {
//                final String result = response.body().string();
//                //ui必须在主线程处理 //原因:若在子线程处理会报运行时错误
//                runOnUiThread(new Runnable() {
//                    @Override
//                    public void run() {
//                        showToast(result);
//                    }
//                });
//            }
//        });
//    }
}

RegisterActivity

package com.ttit.myapp.activity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.ttit.myapp.R;
import com.ttit.myapp.api.Api;
import com.ttit.myapp.api.ApiConfig;
import com.ttit.myapp.api.TtitCallback;
import com.ttit.myapp.util.StringUtils;

import java.util.HashMap;

public class RegisterActivity extends BaseActivity {

    private Button btnRegister;
    private EditText etAccount;
    private EditText etPwd;

    @Override
    protected int initLayout() {
        return R.layout.activity_register;
    }

    @Override
    protected void initView() {

        etAccount = findViewById(R.id.et_account);
        etPwd = findViewById(R.id.et_pwd);
        btnRegister = findViewById(R.id.btn_register);
    }

    @Override
    protected void initData() {
        btnRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String account = etAccount.getText().toString().trim();
                String pwd = etPwd.getText().toString().trim();
                register(account, pwd);
            }
        });
    }
    //注册验证
    private void register(String account, String pwd) {
        if (StringUtils.isEmpty(account)) {
            showToast("请输入账号");
            return;
        }
        if (StringUtils.isEmpty(pwd)) {
            showToast("请输入密码");
            return;
        }
        HashMap<String, Object> params = new HashMap<String, Object>();
        params.put("mobile", account);
        params.put("password", pwd);
        Api.config(ApiConfig.REGISTER, params).postRequest(this,new TtitCallback() {
            @Override
            public void onSuccess(final String res) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        showToast(res);
                    }
                });
            }

            @Override
            public void onFailure(Exception e) {
                Log.e("onFailure", e.toString());
            }
        });
    }
}

MainActivity

package com.ttit.myapp;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.ttit.myapp.activity.BaseActivity;
import com.ttit.myapp.activity.LoginActivity;
import com.ttit.myapp.activity.RegisterActivity;

public class MainActivity extends BaseActivity {
    private Button btnLogin;
    private Button btnRegister;

    @Override
    protected int initLayout() {
        return R.layout.activity_main;
    }

    @Override
    protected void initView() {
        btnLogin = findViewById(R.id.btn_login);
        btnRegister = findViewById(R.id.btn_register);
    }

    @Override
    protected void initData() {
        //登录按钮

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                Intent intent = new Intent(MainActivity.this, LoginActivity.class);
//                startActivity(intent);
                navigateTo(LoginActivity.class);
            }
        });
        //注册按钮

        btnRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                Intent intent = new Intent(MainActivity.this, RegisterActivity.class);
//                startActivity(intent);
                navigateTo(RegisterActivity.class);
            }
        });
    }
}

测试运行

首页界面UI

根据标题创建对应frag

  1. 创建页面的frag
  2. 创建适配器
  3. frag装入适配器
  4. 为分页设置该设配器

布局文件

fragment_home.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tl="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"
    android:orientation="vertical"
    tools:context=".fragment.HomeFragment">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="115dp"
        android:background="#344261"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="39dp"
            android:layout_marginLeft="24dp"
            android:layout_marginTop="26dp"
            android:layout_marginRight="24dp"
            android:background="@drawable/shape_search_box"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <ImageView
                android:layout_width="22dp"
                android:layout_height="22dp"
                android:layout_marginLeft="13dp"
                android:src="@mipmap/search" />

            <EditText
                android:id="@+id/et_search"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginLeft="11dp"
                android:background="@null"
                android:hint="搜索你喜欢的视频"
                android:textColor="@color/black"
                android:textColorHint="#737373"
                android:textSize="15sp" />

        </LinearLayout>

        <com.flyco.tablayout.SlidingTabLayout
            android:id="@+id/slidingTabLayout"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            tl:tl_indicator_corner_radius="1.5dp"
            tl:tl_indicator_height="3dp"
            tl:tl_indicator_width="17dp"
            tl:tl_textSelectColor="#fdf299"
            tl:tl_textUnselectColor="#ffffff"
            tl:tl_indicator_color="#fdf299"
            tl:tl_textsize="16sp" />

    </LinearLayout>

    <com.ttit.myapp.view.FixedViewPager
        android:id="@+id/fixedViewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

//两个控件:滑动导航、tab导航

fragment_video.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical"
    tools:context=".fragment.VideoFragment">

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textColor="@color/black"
        android:textSize="30sp"/>

</FrameLayout>

HomeFragment

/**
 * @author wuzhideren
 */
package com.ttit.myapp.fragment;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;

import com.flyco.tablayout.SlidingTabLayout;
import com.ttit.myapp.R;
import com.ttit.myapp.adapter.HomeAdapter;

import java.util.ArrayList;

public class HomeFragment extends Fragment {
    private ArrayList<Fragment> mFragments = new ArrayList<>();
    private final String[] mTitles = {
            "热门", "ios", "我的",
            "我的", "我的", "我的", "我的"
    };
    private ViewPager viewPager;
    private SlidingTabLayout slidingTabLayout;

    public static HomeFragment newInstance (){
        HomeFragment fragment = new HomeFragment();
        return fragment;
    }
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_home, container, false);//转为布局文件为对象
        //1 绑定导航栏和viewpager
        viewPager = v.findViewById(R.id.fixedViewPager);
        slidingTabLayout = v.findViewById(R.id.slidingTabLayout);
        return v;
    }

    //重写,当onCreateView执行完毕后再执行
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //根据导航栏标题数量创建frag,传入对应的导航栏标题
        for (String title : mTitles) {
            mFragments.add(VideoFragment.newInstance(title));
        }
        //2 为viewpager设置适配器,该适配器包含frag
        viewPager.setAdapter(new HomeAdapter(getFragmentManager(), mTitles, mFragments));
        //3 绑定导航栏和viewpager,实现滑动效果
        slidingTabLayout.setViewPager(viewPager);
    }
}

VideoFragment

/**
 * @author wuzhideren
 */
package com.ttit.myapp.fragment;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.ttit.myapp.R;

public class VideoFragment extends Fragment {
    private String title;//导航栏文字

    public static VideoFragment newInstance (String title){
        VideoFragment fragment = new VideoFragment();
        //接收传入的title
        fragment.title = title;
        return fragment;
    }
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_video, container, false);//转为布局文件为对象
        //设置导航栏文字,根据传入的title
        TextView tv = v.findViewById(R.id.title);
        tv.setText(title);
        return v;
    }
}

HomeAdapter

package com.ttit.myapp.adapter;

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

import java.util.ArrayList;

//用于设置pager的页面
public class HomeAdapter extends FragmentPagerAdapter {

    private String[] mTitles;
    private ArrayList<Fragment> mFragments;
    //通过构造器传入页面属性,以设置
    public HomeAdapter(FragmentManager fm, String[] titles, ArrayList<Fragment> fragments) {
        super(fm);
        this.mTitles = titles;
        this.mFragments = fragments;
    }

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

    @Override
    public CharSequence getPageTitle(int position) {
        return mTitles[position];
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }
}

FixedViewPager

package com.ttit.myapp.view;

import android.content.Context;
import android.util.AttributeSet;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;

public class FixedViewPager extends ViewPager {
    public FixedViewPager(@NonNull Context context) {
        super(context);
    }

    public FixedViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setCurrentItem(int item) {
        super.setCurrentItem(item, false);
    }
}

测试运行

image-20220118154504769

解决闪退问题

// 语法-预加载fragment:

//解决闪退问题-预加载
        viewPager.setOffscreenPageLimit(mFragments.size());

HomeActivity

@Override
    protected void initData() {
        //添加fra实例化对象入集合
        mFragments.add(HomeFragment.newInstance());
        mFragments.add(CollectFragment.newInstance());
        mFragments.add(MyFragment.newInstance());
        //添加tab实例化对象入集合
        for (int i = 0; i < mTitles.length; i++) {
            mTabEntities.add(new TabEntity(mTitles[i], mIconSelectIds[i], mIconUnselectIds[i]));
        }
        //实现点击tab,页面切换
        commonTabLayout.setOnTabSelectListener(new OnTabSelectListener() {
            //@position frag中集合的下标
            @Override
            public void onTabSelect(int position) {
                //设置分页的当前页
                viewPager.setCurrentItem(position);
            }

            @Override
            public void onTabReselect(int position) {
            }
        });
        //为tab视图对象,设置tab集合
        commonTabLayout.setTabData(mTabEntities);
        //为pager添加上述内容
        viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager(), mTitles, mFragments));
        //解决闪退问题-预加载
        viewPager.setOffscreenPageLimit(mFragments.size());
        //导航栏对应切换
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            //当页面切换时,传入当前页面坐标,让导航栏对应地切换
            @Override
            public void onPageSelected(int position) {
                commonTabLayout.setCurrentTab(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }

HomeFragment

//重写,当onCreateView执行完毕后再执行
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    //根据导航栏标题数量创建frag,传入对应的导航栏标题
    for (String title : mTitles) {
        mFragments.add(VideoFragment.newInstance(title));
    }
    //解决闪退问题-预加载
    viewPager.setOffscreenPageLimit(mFragments.size());
    //2 为viewpager设置适配器,该适配器包含frag
    viewPager.setAdapter(new HomeAdapter(getFragmentManager(), mTitles, mFragments));
    //3 绑定导航栏和viewpager,实现滑动效果
    slidingTabLayout.setViewPager(viewPager);
}

测试运行

image-20220118154623719

创建数据库

1.安装mysql8 || mysql5.7

2.设置密码和账号
  root   123456

3.使用navicat工具导入myapp.sql文件

4.使用java命令运行Tomcat
  java -jar renren-fast.jar
    
5.修改请求地址为本地地址

建表myapp

image-20220118155051257

导入sql文件

若使用5.7版本:

  1. utf8mb4_0900_ai_ci全部替换为utf8_general_ci
  2. utf8mb4替换为utf8

启动tomc服务

在该文件夹下存放jar包资源

在该文件夹路径下启动下面命令

java -jar renren-fast.jar

image-20220118160827440

启动成功:未终止服务:

image-20220119222111113

修改请求地址

package com.ttit.myapp.api;

public class ApiConfig {
    //本地服务器地址 1本地IP地址
    public static final String BASE_URl = "http://127.0.0.1:8080/renren-fast";
    public static final String LOGIN = "/app/login"; //登录
    public static final String REGISTER = "/app/register";//注册
}

未联网,修改请求地址为本地地址127.0.0.1:8080

若联网,则本地地址不是这个,而是:

image-20220119221219841

测试运行

image-20220119221929360

解决访问网络失败

1 E/onFailure: socket failed: EPERM (Operation not permitted)

  • 添加网络权限
  • 若不行,更换||重装模拟机

2 无法连接到请求地址:

  • 说明请求地址写错了

3 网络超时:

  • 更换请求地址

首页列表

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingLeft="21dp"
        android:paddingTop="13dp"
        android:paddingRight="21dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="42dp"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/img_header"
                android:layout_width="42dp"
                android:layout_height="42dp"
                android:src="@mipmap/header" />

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginLeft="11dp">

                <TextView
                    android:id="@+id/title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentTop="true"
                    android:text="韭菜盒子新做法,不发面不烫面"
                    android:textColor="#242424"
                    android:textSize="14sp" />

                <TextView
                    android:id="@+id/author"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentBottom="true"
                    android:text="大胃王"
                    android:textColor="#9f9f9f"
                    android:textSize="12sp" />

            </RelativeLayout>

    </LinearLayout>

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="187dp"
                android:layout_marginTop="8dp">

                <ImageView
                    android:id="@+id/img_cover"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="fitXY"
                    android:src="@mipmap/default_bg" />

            </RelativeLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="39dp">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:layout_centerVertical="true"
                android:orientation="horizontal">

                <ImageView
                    android:id="@+id/img_comment"
                    android:layout_width="19dp"
                    android:layout_height="19dp"
                    android:src="@mipmap/comment" />

                <TextView
                    android:id="@+id/comment"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="3dp"
                    android:text="0"
                    android:textColor="#161616"
                    android:textSize="14sp" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:layout_centerVertical="true"
                android:orientation="horizontal">

                <ImageView
                    android:id="@+id/img_collect"
                    android:layout_width="19dp"
                    android:layout_height="19dp"
                    android:src="@mipmap/collect" />

                <TextView
                    android:id="@+id/collect"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="3dp"
                    android:text="0"
                    android:textColor="#161616"
                    android:textSize="14sp" />
            </LinearLayout>


            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:orientation="horizontal">

                <ImageView
                    android:id="@+id/img_like"
                    android:layout_width="19dp"
                    android:layout_height="19dp"
                    android:src="@mipmap/dianzan" />

                <TextView
                    android:id="@+id/dz"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="3dp"
                    android:text="0"
                    android:textColor="#161616"
                    android:textSize="14sp" />
            </LinearLayout>
        </RelativeLayout>
    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="7dp"
        android:background="#f5f5f4" />
</LinearLayout>

导入依赖

implementation 'androidx.recyclerview:recyclerview:1.2.0'

// 目的:为了使用列表控件

修改列表布局

fragment_video.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/refreshLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical"
    tools:context=".fragment.VideoFragment">

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

</LinearLayout>

数据公共类

com.ttit.myapp.entity.VideoEntity.java

package com.ttit.myapp.entity;

import java.io.Serializable;

/**
 * @author: wuzhideren
 * @date: 2022
 **/
public class VideoEntity implements Serializable {
    private int id;
    private String title; //标题
    private String name; // 视频名字
    private int dzCount; // 点赞数量
    private int collectCount; //评论数量
    private int commentCount; // 收藏数量

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getName() {
        return name;
    }

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

    public int getDzCount() {
        return dzCount;
    }

    public void setDzCount(int dzCount) {
        this.dzCount = dzCount;
    }

    public int getCollectCount() {
        return collectCount;
    }

    public void setCollectCount(int collectCount) {
        this.collectCount = collectCount;
    }

    public int getCommentCount() {
        return commentCount;
    }

    public void setCommentCount(int commentCount) {
        this.commentCount = commentCount;
    }
}

展示数据

/**
 * @author wuzhideren
 */
package com.ttit.myapp.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.ttit.myapp.R;
import com.ttit.myapp.entity.VideoEntity;

import org.w3c.dom.Text;

import java.util.List;

public class VideoAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private  Context mContext;
    private  List<VideoEntity> datas;

    //1 构造时,接收
    public VideoAdapter(Context context, List<VideoEntity> datas){
        this.mContext = context;
        this.datas = datas;
    }
    //2 设置每一项的布局
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 获取视图对象
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_video_layout, parent, false); // @parent 视图对象集
        ViewHolder viewHolder = new ViewHolder(view);
        // 返回视图控件
        return viewHolder;
    }
    // 4 给视图对象赋值 1 当前对象 2 当前下标,作用:获取当前数据公共类对象
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        // 获取视图对象
        ViewHolder vh = (ViewHolder)holder; // 记得强转
        // 获取数据
        VideoEntity videoEntity = datas.get(position);
        // 设置数据
        vh.tvTitle.setText(videoEntity.getTitle());
        vh.tvAuthor.setText(videoEntity.getName());
        vh.tvDz.setText(String.valueOf(videoEntity.getDzCount()));// 需要转换为字符串
        vh.tvCollect.setText(String.valueOf(videoEntity.getCollectCount()));
        vh.tvComment.setText(String.valueOf(videoEntity.getCommentCount()));
    }
    // 2 设置需要加载模块的数量
    @Override
    public int getItemCount() {
        return datas.size();// 根据传入数据,决定加载的数量。
    }
    // 3 获取视图对象
    static class ViewHolder extends RecyclerView.ViewHolder {
        private TextView tvTitle;
        private TextView tvAuthor;
        private TextView tvDz;
        private TextView tvCollect;
        private TextView tvComment;
        // 传入布局对象
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            tvTitle = itemView.findViewById(R.id.title);
            tvAuthor = itemView.findViewById(R.id.author);
            tvDz = itemView.findViewById(R.id.dz);
            tvCollect = itemView.findViewById(R.id.collect);
            tvComment = itemView.findViewById(R.id.comment);
        }
    }
}

传入数据

位置:com/ttit/myapp/adapter/VideoAdapter.java

@Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_video, container, false);//转为布局文件为对象
//        //设置导航栏文字,根据传入的title
//        TextView tv = v.findViewById(R.id.title);
//        tv.setText(title);
        // 配置列表布局
        RecyclerView recyclerView = v.findViewById(R.id.recyclerView);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());// 获取父活动对象
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);// 设置为垂直排列
        recyclerView.setLayoutManager(linearLayoutManager);// 为视图对象,设置布局管理器
        // 传入数据
            // 设置数据
        List<VideoEntity> datas = new ArrayList<>();
        for (int i = 0; i < 8; i++) {
            VideoEntity videoEntity  = new VideoEntity();
            videoEntity.setTitle("韭菜盒子");
            videoEntity.setName("韭菜盒子");
            videoEntity.setDzCount(i * 2);
            videoEntity.setCollectCount(i * 4);
            videoEntity.setCommentCount(i * 6);
            datas.add(videoEntity);
        }
            // 传入数据
        VideoAdapter videoAdapter = new VideoAdapter(getActivity(), datas);
          // 添加布局到 主页列表控件
        recyclerView.setAdapter(videoAdapter);
        return v;
    }

测试运行

image-20220119181347815

视频接口

com/ttit/myapp/api/Api.java

public void getRequest(final TtitCallback callback) {
        String url = getAppendUrl(requestUrl, mParams);
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("onFailure", e.getMessage());
                callback.onFailure(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String result = response.body().string();
                callback.onSuccess(result);
            }
        });
    }
    public void getRequest(Context context, final TtitCallback callback) {
        SharedPreferences sp = context.getSharedPreferences("sp_ttit", MODE_PRIVATE);
        String token = sp.getString("token", "");
        String url = getAppendUrl(requestUrl, mParams);
        Request request = new Request.Builder()
                .url(url)
                .addHeader("token", token)
                .get()
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("onFailure", e.getMessage());
                callback.onFailure(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String result = response.body().string();
                try {
                    JSONObject jsonObject = new JSONObject(result);
                    String code = jsonObject.getString("code");
                    if (code.equals("401")) {
                        Intent in = new Intent(context, LoginActivity.class);
                        context.startActivity(in);
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                callback.onSuccess(result);
            }
        });
    }

    private String getAppendUrl(String url, Map<String, Object> map) {
        if (map != null && !map.isEmpty()) {
            StringBuffer buffer = new StringBuffer();
            Iterator<Entry<String, Object>> iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
                Entry<String, Object> entry = iterator.next();
                if (StringUtils.isEmpty(buffer.toString())) {
                    buffer.append("?");
                } else {
                    buffer.append("&");
                }
                buffer.append(entry.getKey()).append("=").append(entry.getValue());
            }
            url += buffer.toString();
        }
        return url;
    }

获取数据

com.ttit.myapp.api.ApiConfig

public static final String VIDEO_LIST = "/app/videolist/list";//所有类型视频列表

BaseFragment

com.ttit.myapp.fragment.BaseFragment

复制BaseActivity内容,修改上下文对象为getActivity()

/**
 * @author wuzhideren
 */
package com.ttit.myapp.fragment;


import static android.content.Context.MODE_PRIVATE;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Looper;
import android.widget.Toast;

import androidx.fragment.app.Fragment;

public class BaseFragment extends Fragment {
    //提示信息
    public void showToast(String msg) {
        Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
    }

    //处理子线程ui
    public void showToastSync(String msg) {
        //令子线程可以有ui显示,而不报错
        Looper.prepare();
        Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
        Looper.loop();//易错修正:其后代码不会立即执行
    }

    //活动跳转
    public void navigateTo(Class cls) {
        Intent in = new Intent(getActivity(), cls);
        startActivity(in);
    }

    //sp存储
    protected void insertVal(String key, String val) {
        SharedPreferences sp = getActivity().getSharedPreferences("sp_ttit", MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        editor.putString(key, val);
        editor.commit();
    }
    //取出sq数据
    protected String getStringFromSp(String key){
        SharedPreferences sp = getActivity().getSharedPreferences("sp_ttit", MODE_PRIVATE);
        return sp.getString(key,"");
    }
}

传入数据

  1. 优化代码而封装
  2. 判断情况
  3. 传入数据
/**
 * @author wuzhideren
 */
package com.ttit.myapp.fragment;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

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

import com.google.gson.Gson;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import com.ttit.myapp.R;
import com.ttit.myapp.activity.LoginActivity;
import com.ttit.myapp.adapter.VideoAdapter;
import com.ttit.myapp.api.Api;
import com.ttit.myapp.api.ApiConfig;
import com.ttit.myapp.api.TtitCallback;
import com.ttit.myapp.entity.VideoEntity;
import com.ttit.myapp.entity.VideoListResponse;
import com.ttit.myapp.util.StringUtils;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;

public class VideoFragment extends BaseFragment {
    private String title;//导航栏文字
    private RecyclerView recyclerView;
    private RefreshLayout refreshLayout;

    public static VideoFragment newInstance (String title){
        VideoFragment fragment = new VideoFragment();
        //接收传入的title
        fragment.title = title;
        return fragment;
    }
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_video, container, false);//转为布局文件为对象
//        //设置导航栏文字,根据传入的title
//        TextView tv = v.findViewById(R.id.title);
//        tv.setText(title);
        // 配置列表布局
        recyclerView = v.findViewById(R.id.recyclerView);
        refreshLayout = v.findViewById(R.id.refreshLayout);
        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(RefreshLayout refreshlayout) {
                refreshlayout.finishRefresh(2000/*,false*/);//传入false表示刷新失败
            }
        });
        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(RefreshLayout refreshlayout) {
                refreshlayout.finishLoadMore(2000/*,false*/);//传入false表示加载失败
            }
        });
        return v;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());// 获取父活动对象
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);// 设置为垂直排列
        recyclerView.setLayoutManager(linearLayoutManager);// 为视图对象,设置布局管理器
        // 传入数据
        // 设置数据
//        List<VideoEntity> datas = new ArrayList<>();
//        for (int i = 0; i < 8; i++) {
//            VideoEntity videoEntity  = new VideoEntity();
//            videoEntity.setTitle("韭菜盒子");
//            videoEntity.setName("韭菜盒子");
//            videoEntity.setDzCount(i * 2);
//            videoEntity.setCollectCount(i * 4);
//            videoEntity.setCommentCount(i * 6);
//            datas.add(videoEntity);
//        }
        getVideoList();
    }

    //获取视频列表数据
    private void getVideoList() {
        String token = getStringFromSp("token");
        //如果登录账户不为空
        if (!StringUtils.isEmpty(token)){
            HashMap<String, Object> params = new HashMap<>();//注意:是String
            params.put("token", token);
            Api.config(ApiConfig.VIDEO_LIST, params).getRequest(new TtitCallback(){

                @Override
                public void onSuccess(String res) {
                    VideoListResponse response = new Gson().fromJson(res, VideoListResponse.class);
                    if (response != null && response.getCode() == 0) {
                        List<VideoEntity> datas = response.getPage().getList();
                        // 传入数据
                        VideoAdapter videoAdapter = new VideoAdapter(getActivity(), datas);
                        // 添加布局到 主页列表控件
                        recyclerView.setAdapter(videoAdapter);
//                        showToastSync(res);
                    }
                }

                @Override
                public void onFailure(Exception e) {

                }
            });
        }else {//说明没有登录
            navigateTo(LoginActivity.class);
        }
    }
}

封装代码

VideoListResponse

package com.ttit.myapp.entity;

import java.io.Serializable;
import java.util.List;

/**
 * @author: wuzhi
 * @date: 2022
 **/
public class VideoListResponse implements Serializable {

    /**
     * msg : success
     * code : 0
     * page : {"totalCount":4,"pageSize":10,"totalPage":1,"currPage":1,"list":[{"vid":1,"vtitle":"青龙战甲搭配机动兵,P城上空肆意1V4","author":"狙击手麦克","coverurl":"https://sf3-xgcdn-tos.pstatp.com/img/tos-cn-i-0004/527d013205a74eb0a77202d7a9d5b511~tplv-crop-center:1041:582.jpg","headurl":"https://sf1-ttcdn-tos.pstatp.com/img/pgc-image/c783a73368fa4666b7842a635c63a8bf~360x360.image","commentNum":210,"likeNum":23,"collectNum":100},{"vid":2,"vtitle":"【仁王2】视频攻略 2-3 虚幻魔城","author":"黑桐谷歌","coverurl":"https://lf1-xgcdn-tos.pstatp.com/img/tos-cn-p-0000/9ff7fe6c89e44ca3a22aad5744e569e3~tplv-crop-center:1041:582.jpg","headurl":"https://sf6-ttcdn-tos.pstatp.com/img/mosaic-legacy/8110/752553978~360x360.image","commentNum":1300,"likeNum":500,"collectNum":120},{"vid":3,"vtitle":"最猛暴击吕布教学,这才是战神该有的样子","author":"小凡解说游戏","coverurl":"https://sf1-xgcdn-tos.pstatp.com/img/tos-cn-i-0004/83cc11d5e26047c6b0ead149f41a8266~tplv-crop-center:1041:582.jpg","headurl":"https://p3.pstatp.com/large/a14a000405f16e51842f","commentNum":10,"likeNum":19,"collectNum":5},{"vid":4,"vtitle":"拳皇14:小孩输掉一分,印尼选手得意忘形","author":"E游未尽小E","coverurl":"https://sf1-xgcdn-tos.pstatp.com/img/tos-cn-i-0004/b9553b7a28d94f27a7115157797b52ff~tplv-crop-center:1041:582.jpg","headurl":"https://sf3-ttcdn-tos.pstatp.com/img/pgc-image/f6b840d23f9e465bb5ac9e570b28321d~360x360.image","commentNum":22,"likeNum":180,"collectNum":963}]}
     */

    private String msg;
    private int code;
    private PageBean page;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

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

    public PageBean getPage() {
        return page;
    }

    public void setPage(PageBean page) {
        this.page = page;
    }

    public static class PageBean {
        /**
         * totalCount : 4
         * pageSize : 10
         * totalPage : 1
         * currPage : 1
         * list : [{"vid":1,"vtitle":"青龙战甲搭配机动兵,P城上空肆意1V4","author":"狙击手麦克","coverurl":"https://sf3-xgcdn-tos.pstatp.com/img/tos-cn-i-0004/527d013205a74eb0a77202d7a9d5b511~tplv-crop-center:1041:582.jpg","headurl":"https://sf1-ttcdn-tos.pstatp.com/img/pgc-image/c783a73368fa4666b7842a635c63a8bf~360x360.image","commentNum":210,"likeNum":23,"collectNum":100},{"vid":2,"vtitle":"【仁王2】视频攻略 2-3 虚幻魔城","author":"黑桐谷歌","coverurl":"https://lf1-xgcdn-tos.pstatp.com/img/tos-cn-p-0000/9ff7fe6c89e44ca3a22aad5744e569e3~tplv-crop-center:1041:582.jpg","headurl":"https://sf6-ttcdn-tos.pstatp.com/img/mosaic-legacy/8110/752553978~360x360.image","commentNum":1300,"likeNum":500,"collectNum":120},{"vid":3,"vtitle":"最猛暴击吕布教学,这才是战神该有的样子","author":"小凡解说游戏","coverurl":"https://sf1-xgcdn-tos.pstatp.com/img/tos-cn-i-0004/83cc11d5e26047c6b0ead149f41a8266~tplv-crop-center:1041:582.jpg","headurl":"https://p3.pstatp.com/large/a14a000405f16e51842f","commentNum":10,"likeNum":19,"collectNum":5},{"vid":4,"vtitle":"拳皇14:小孩输掉一分,印尼选手得意忘形","author":"E游未尽小E","coverurl":"https://sf1-xgcdn-tos.pstatp.com/img/tos-cn-i-0004/b9553b7a28d94f27a7115157797b52ff~tplv-crop-center:1041:582.jpg","headurl":"https://sf3-ttcdn-tos.pstatp.com/img/pgc-image/f6b840d23f9e465bb5ac9e570b28321d~360x360.image","commentNum":22,"likeNum":180,"collectNum":963}]
         */

        private int totalCount;
        private int pageSize;
        private int totalPage;
        private int currPage;
        private List<VideoEntity> list;

        public int getTotalCount() {
            return totalCount;
        }

        public void setTotalCount(int totalCount) {
            this.totalCount = totalCount;
        }

        public int getPageSize() {
            return pageSize;
        }

        public void setPageSize(int pageSize) {
            this.pageSize = pageSize;
        }

        public int getTotalPage() {
            return totalPage;
        }

        public void setTotalPage(int totalPage) {
            this.totalPage = totalPage;
        }

        public int getCurrPage() {
            return currPage;
        }

        public void setCurrPage(int currPage) {
            this.currPage = currPage;
        }

        public List<VideoEntity> getList() {
            return list;
        }

        public void setList(List<VideoEntity> list) {
            this.list = list;
        }
    }
}

VideoEntity

package com.ttit.myapp.entity;

import java.io.Serializable;

/**
 * @author: wuzhideren
 * @date: 2022
 **/
public class VideoEntity implements Serializable {
    /**
     * vid : 1
     * vtitle : 青龙战甲搭配机动兵,P城上空肆意1V4
     * author : 狙击手麦克
     * coverurl : http://sf3-xgcdn-tos.pstatp.com/img/tos-cn-i-0004/527d013205a74eb0a77202d7a9d5b511~tplv-crop-center:1041:582.jpg
     * headurl : https://sf1-ttcdn-tos.pstatp.com/img/pgc-image/c783a73368fa4666b7842a635c63a8bf~360x360.image
     * playurl : http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4
     * createTime : 2020-07-14 11:21:45
     * updateTime : 2020-07-19 12:05:33
     * categoryId : 1
     * categoryName : 游戏
     * videoSocialEntity : {"commentnum":103,"likenum":121,"collectnum":220}
     */

    private int vid;
    private String vtitle;
    private String author;
    private String coverurl;
    private String headurl;
    private String commentnum;
    private String likenum;
    private String collectnum;

    public int getVid() {
        return vid;
    }

    public void setVid(int vid) {
        this.vid = vid;
    }

    public String getVtitle() {
        return vtitle;
    }

    public void setVtitle(String vtitle) {
        this.vtitle = vtitle;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getCoverurl() {
        return coverurl;
    }

    public void setCoverurl(String coverurl) {
        this.coverurl = coverurl;
    }

    public String getHeadurl() {
        return headurl;
    }

    public void setHeadurl(String headurl) {
        this.headurl = headurl;
    }

    public String getCommentnum() {
        return commentnum;
    }

    public void setCommentnum(String commentnum) {
        this.commentnum = commentnum;
    }

    public String getLikenum() {
        return likenum;
    }

    public void setLikenum(String likenum) {
        this.likenum = likenum;
    }

    public String getCollectnum() {
        return collectnum;
    }

    public void setCollectnum(String collectnum) {
        this.collectnum = collectnum;
    }
}

com.ttit.myapp.fragment.VideoFragment

修改代码

@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        // 获取视图对象
        ViewHolder vh = (ViewHolder)holder; // 记得强转
        // 获取数据
        VideoEntity videoEntity = datas.get(position);
        // 设置数据
        vh.tvTitle.setText(videoEntity.getVtitle());
        vh.tvAuthor.setText(videoEntity.getAuthor());
        vh.tvDz.setText(String.valueOf(videoEntity.getVideoSocialEntity().getLikenum()));// 需要转换为字符串
        vh.tvCollect.setText(String.valueOf(videoEntity.getVideoSocialEntity().getCollectnum()));
        vh.tvComment.setText(String.valueOf(videoEntity.getVideoSocialEntity().getCommentnum()));

添加依赖

目的:异步加载图片,加载网络图片资源

implementation 'com.squareup.picasso:picasso:2.5.2'

异步加载图片

@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        // 获取视图对象
        ViewHolder vh = (ViewHolder)holder; // 记得强转
        // 获取数据
        VideoEntity videoEntity = datas.get(position);
        // 设置数据
        vh.tvTitle.setText(videoEntity.getVtitle());
        vh.tvAuthor.setText(videoEntity.getAuthor());
        vh.tvDz.setText(String.valueOf(videoEntity.getVideoSocialEntity().getLikenum()));// 需要转换为字符串
        vh.tvCollect.setText(String.valueOf(videoEntity.getVideoSocialEntity().getCollectnum()));
        vh.tvComment.setText(String.valueOf(videoEntity.getVideoSocialEntity().getCommentnum()));
        // 异步加载网络图片
        Picasso.with(mContext).load(videoEntity.getHeadurl()).into(vh.imgHeader);
        Picasso.with(mContext).load(videoEntity.getCoverurl()).into(vh.imgCover);
    }
    // 2 设置需要加载模块的数量
    @Override
    public int getItemCount() {
        return datas.size();// 根据传入数据,决定加载的数量。
    }
    // 3 获取视图对象
    static class ViewHolder extends RecyclerView.ViewHolder {
        private TextView tvTitle;
        private TextView tvAuthor;
        private TextView tvDz;
        private TextView tvCollect;
        private TextView tvComment;
        // 获取图片资源
        private ImageView imgHeader;
        private ImageView imgCover;
        // 传入布局对象
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            tvTitle = itemView.findViewById(R.id.title);
            tvAuthor = itemView.findViewById(R.id.author);
            tvDz = itemView.findViewById(R.id.dz);
            tvCollect = itemView.findViewById(R.id.collect);
            tvComment = itemView.findViewById(R.id.comment);
            imgHeader = itemView.findViewById(R.id.img_header);
            imgCover = itemView.findViewById(R.id.img_cover);
        }
    }

圆形图片

利用毕加索插件,实现方法,重新绘制图片

package com.ttit.myapp.view;

import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;

import com.squareup.picasso.Transformation;

/**
 * @author: wuzhideren
 * @date: 2022
 **/
public class CircleTransform implements Transformation {

    @Override
    public Bitmap transform(Bitmap source) {
        int size = Math.min(source.getWidth(), source.getHeight());
        int x = (source.getWidth() - size) / 2;
        int y = (source.getHeight() - size) / 2;
        Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size);
        if (squaredBitmap != source) {
            source.recycle();
        }
        Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig());
        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint();
        BitmapShader shader = new BitmapShader(squaredBitmap,
                BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP);
        paint.setShader(shader);
        paint.setAntiAlias(true);
        float r = size / 2f;
        canvas.drawCircle(r, r, r, paint);
        squaredBitmap.recycle();
        return bitmap;
    }

    @Override
    public String key() {
        return "circle";
    }
}

修改com.ttit.myapp.adapter.VideoAdapter

// 异步加载网络图片
        Picasso.with(mContext)
                .load(videoEntity.getHeadurl())
                .transform(new CircleTransform())
                .into(vh.imgHeader);
        Picasso.with(mContext).load(videoEntity.getCoverurl())
                .transform(new CircleTransform())
                .into(vh.imgCover);

测试运行

image-20220121225608097

加载动画

RefreshLayout依赖

implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.3'  //1.0.5及以前版本的老用户升级需谨慎,API改动过大

https://github.com/scwang90/SmartRefreshLayout/tree/1.x#%E7%AE%80%E5%8D%95%E7%94%A8%E4%BE%8B

修改根元素

layout/fragment_video.xml

<?xml version="1.0" encoding="utf-8"?>
<com.scwang.smartrefresh.layout.SmartRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/refreshLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical"
    tools:context=".fragment.VideoFragment">

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

</com.scwang.smartrefresh.layout.SmartRefreshLayout>

配置视图对象

com.ttit.myapp.fragment.VideoFragment

@Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_video, container, false);//转为布局文件为对象
//        //设置导航栏文字,根据传入的title
//        TextView tv = v.findViewById(R.id.title);
//        tv.setText(title);
        // 配置列表布局
        recyclerView = v.findViewById(R.id.recyclerView);
        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(RefreshLayout refreshlayout) {
                refreshlayout.finishRefresh(2000/*,false*/);//传入false表示刷新失败
            }
        });
        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(RefreshLayout refreshlayout) {
                refreshlayout.finishLoadMore(2000/*,false*/);//传入false表示加载失败
            }
        });
        return v;
    }

测试运行

image-20220121225627680

分页功能

获取分页数据

com.ttit.myapp.fragment.VideoFragment

//获取视频列表数据
    private void getVideoList(boolean isRefresh) {//@isRefresh 分页-1 区分刷新还是下拉加载
        String token = getStringFromSp("token");
        //如果登录账户不为空
        if (!StringUtils.isEmpty(token)){
            HashMap<String, Object> params = new HashMap<>();//注意:是String,这是请求数据的容器
            params.put("token", token);
            params.put("page", pageNum);// 总页数
            params.put("limit", ApiConfig.PAGE_SIZE);// 每页数量

传入分页数据

if (response != null && response.getCode() == 0) { // 如果返回数据正常
    List<VideoEntity> list = response.getPage().getList(); // 获取格式化后返回数据
    if (list != null && list.size() > 0){ // 如果有数据
        if (isRefresh){// 如果时刷新
            datas = list;
        } else { // 如果加载
            datas.addAll(list); // 追加加载后的数据
        }
        videoAdapter.setDatas(datas);
        videoAdapter.notifyDataSetChanged();// 通知刷新数据
    }else {
        if (isRefresh){
            showToastSync("暂时无数据");
        }else {
            showToastSync("没有更多数据");
        }
    }
}

接收分页数据

com.ttit.myapp.api.ApiConfig

 // 接收传入数据
    public void setDatas(List<VideoEntity> datas) {
        this.datas = datas;
    }

    //1 构造时,不接收
    public VideoAdapter(Context context){
        this.mContext = context;
    }

    //1 构造时,接收
    public VideoAdapter(Context context, List<VideoEntity> datas){
        this.mContext = context;
        this.datas = datas;
    }

解决没有数据时的报错

 @Override
    public int getItemCount() {
        if (datas != null){
            return datas.size();// 根据传入数据,决定加载的数量。
        }
        return 0;
    }

加载动画联动分页

这是完整代码

/**
 * @author wuzhideren
 */
package com.ttit.myapp.fragment;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

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

import com.google.gson.Gson;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import com.ttit.myapp.R;
import com.ttit.myapp.activity.LoginActivity;
import com.ttit.myapp.adapter.VideoAdapter;
import com.ttit.myapp.api.Api;
import com.ttit.myapp.api.ApiConfig;
import com.ttit.myapp.api.TtitCallback;
import com.ttit.myapp.entity.VideoEntity;
import com.ttit.myapp.entity.VideoListResponse;
import com.ttit.myapp.util.StringUtils;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;

public class VideoFragment extends BaseFragment {
    private String title;//导航栏文字
    private RecyclerView recyclerView;
    private RefreshLayout refreshLayout;
    private int pageNum = 1; // 页数
    List<VideoEntity> datas = new ArrayList<>(); // 传入的数据
    VideoAdapter videoAdapter;

    public static VideoFragment newInstance (String title){
        VideoFragment fragment = new VideoFragment();
        //接收传入的title
        fragment.title = title;
        return fragment;
    }
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_video, container, false);//转为布局文件为对象
//        //设置导航栏文字,根据传入的title
//        TextView tv = v.findViewById(R.id.title);
//        tv.setText(title);
        // 配置列表布局
        recyclerView = v.findViewById(R.id.recyclerView);
        refreshLayout = v.findViewById(R.id.refreshLayout);
        return v;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());// 获取父活动对象
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);// 设置为垂直排列
        recyclerView.setLayoutManager(linearLayoutManager);// 为视图对象,设置布局管理器
        // 创建设配器
        videoAdapter = new VideoAdapter(getActivity());
        // 添加布局到 主页列表控件
        recyclerView.setAdapter(videoAdapter);
        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(RefreshLayout refreshlayout) {// 过程:下拉刷新时,触发该方法
//                refreshlayout.finishRefresh(2000/*,false*/);//传入false表示刷新失败
                pageNum = 1; // 每次刷新 页数重置为1,就是回到首页
                getVideoList(true); // 传入数据
            }
        });
        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(RefreshLayout refreshlayout) {
//                refreshlayout.finishLoadMore(2000/*,false*/);//传入false表示加载失败
                pageNum ++; // 每加载一次,下一页
                getVideoList(false);// 更新传入的数据
            }
        });
        getVideoList(true);// 分页-2 第一次进来刷新
    }

    //获取视频列表数据
    private void getVideoList(boolean isRefresh) {//@isRefresh 分页-1 区分刷新还是下拉加载
        String token = getStringFromSp("token");
        //如果登录账户不为空
        if (!StringUtils.isEmpty(token)){
            HashMap<String, Object> params = new HashMap<>();//注意:是String,这是请求数据的容器
            params.put("token", token);
            params.put("page", pageNum);// 总页数
            params.put("limit", ApiConfig.PAGE_SIZE);// 每页数量
            Api.config(ApiConfig.VIDEO_LIST, params).getRequest(new TtitCallback(){

                @Override
                public void onSuccess(String res) {
                    getActivity().runOnUiThread(new Runnable() {//解决子线程无法加载ui问题

                        @Override
                        public void run() {
                            if (isRefresh){// 请求成功就关闭加载动画
                                refreshLayout.finishRefresh();
                            }else {
                                refreshLayout.finishLoadMore();// 注意!别和上面写成一样了
                            }
                            VideoListResponse response = new Gson().fromJson(res, VideoListResponse.class);
                            if (response != null && response.getCode() == 0) { // 如果返回数据正常
                                List<VideoEntity> list = response.getPage().getList(); // 获取格式化后返回数据
                                if (list != null && list.size() > 0){ // 如果有数据
                                    if (isRefresh){// 如果时刷新
                                        datas = list;
                                    } else { // 如果加载
                                        datas.addAll(list); // 追加加载后的数据
                                    }
                                    videoAdapter.setDatas(datas);
                                    videoAdapter.notifyDataSetChanged();// 通知刷新数据
                                }else {
                                    if (isRefresh){
//                                        showToastSync();// 注意!不能使用
                                        showToast("暂时无数据");
                                    }else {
                                        showToast("没有更多数据");
                                    }
                                }
                            }
                        }
                    });
                }

                @Override
                public void onFailure(Exception e) {
                    if (isRefresh){
                        refreshLayout.finishRefresh();
                    }else {
                        refreshLayout.finishLoadMore();
                    }

                }
            });
        }else {//说明没有登录
            navigateTo(LoginActivity.class);
        }
    }
}

代码优化

com.ttit.myapp.fragment.BaseFragment

修改基类为抽象类,编写三个抽象方法,主要接收布局文件以加载

public abstract class BaseFragment extends Fragment {
    protected View mRootView;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (mRootView == null) {
            mRootView = inflater.inflate(initLayout(), container, false);
            initView();
        }
        return mRootView;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initData();
    }

    protected abstract int initLayout();

    protected abstract void initView();

    protected abstract void initData();

继承类重写抽象方法,整理代码,传入布局文件

@Override
    protected int initLayout() {
        return R.layout.fragment_video;
    }

    @Override
    protected void initView() {
        recyclerView = mRootView.findViewById(R.id.recyclerView);
        refreshLayout = mRootView.findViewById(R.id.refreshLayout);
    }

    // 过程与onViewCreated一样
    @Override
    protected void initData() {
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());// 获取父活动对象
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);// 设置为垂直排列
        recyclerView.setLayoutManager(linearLayoutManager);// 为视图对象,设置布局管理器
        // 创建设配器
        videoAdapter = new VideoAdapter(getActivity());
        // 添加布局到 主页列表控件
        recyclerView.setAdapter(videoAdapter);
        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(RefreshLayout refreshlayout) {// 过程:下拉刷新时,触发该方法
//                refreshlayout.finishRefresh(2000/*,false*/);//传入false表示刷新失败
                pageNum = 1; // 每次刷新 页数重置为1,就是回到首页
                getVideoList(true); // 传入数据
            }
        });
        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(RefreshLayout refreshlayout) {
//                refreshlayout.finishLoadMore(2000/*,false*/);//传入false表示加载失败
                pageNum ++; // 每加载一次,下一页
                getVideoList(false);// 更新传入的数据
            }
        });
        getVideoList(true);// 分页-2 第一次进来刷新
    }

测试运行

视频功能

protected VideoView mVideoView;
protected StandardVideoController mController;
protected ErrorView mErrorView;
protected CompleteView mCompleteView;
protected TitleView mTitleView;

视频信息依赖

implementation 'com.github.dueeeke.dkplayer:dkplayer-java:3.2.6'
implementation 'com.github.dueeeke.dkplayer:dkplayer-ui:3.2.6'
implementation 'com.github.dueeeke.dkplayer:player-exo:3.2.6'
implementation 'com.github.dueeeke.dkplayer:player-ijk:3.2.6'
implementation 'com.github.dueeeke.dkplayer:videocache:3.2.6'
import android.widget.VideoView; // 注意:别错包!!!
// 正确:
import com.dueeeke.videoplayer.player.VideoView;
/**
 * 当前播放的位置
 */
protected int mCurPos = -1;
/**
 * 上次播放的位置,用于页面切回来之后恢复播放
 */
protected int mLastPos = mCurPos;

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 0:
                videoAdapter.setDatas(datas);
                videoAdapter.notifyDataSetChanged();
                break;
        }
    }
};
// 视频视图信息配置
protected void initVideoView() {
    mVideoView = new VideoView(getActivity());
    mVideoView.setOnStateChangeListener(new com.dueeeke.videoplayer.player.VideoView.SimpleOnStateChangeListener() {
        @Override
        public void onPlayStateChanged(int playState) {
            //监听VideoViewManager释放,重置状态
            if (playState == com.dueeeke.videoplayer.player.VideoView.STATE_IDLE) {
                Utils.removeViewFormParent(mVideoView);
                mLastPos = mCurPos;
                mCurPos = -1;
            }
        }
    });
    mController = new StandardVideoController(getActivity());
    mErrorView = new ErrorView(getActivity());
    mController.addControlComponent(mErrorView);
    mCompleteView = new CompleteView(getActivity());
    mController.addControlComponent(mCompleteView);
    mTitleView = new TitleView(getActivity());
    mController.addControlComponent(mTitleView);
    mController.addControlComponent(new VodControlView(getActivity()));
    mController.addControlComponent(new GestureView(getActivity()));
    mController.setEnableOrientation(true);
    mVideoView.setVideoController(mController);
}
recyclerView.setAdapter(videoAdapter);
recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
    @Override
    public void onChildViewAttachedToWindow(@NonNull View view) {

    }

    @Override
    public void onChildViewDetachedFromWindow(@NonNull View view) {
        FrameLayout playerContainer = view.findViewById(R.id.player_container);
        View v = playerContainer.getChildAt(0);
        if (v != null && v == mVideoView && !mVideoView.isFullScreen()) {
            releaseVideoView();
        }
    }
});
@Override
public void onPause() {
    super.onPause();
    pause();
}

/**
 * 由于onPause必须调用super。故增加此方法,
 * 子类将会重写此方法,改变onPause的逻辑
 */
protected void pause() {
    releaseVideoView();
}

@Override
public void onResume() {
    super.onResume();
    resume();
}

/**
 * 由于onResume必须调用super。故增加此方法,
 * 子类将会重写此方法,改变onResume的逻辑
 */
protected void resume() {
    if (mLastPos == -1)
        return;
    //恢复上次播放的位置
    startPlay(mLastPos);
}

/**
 * PrepareView被点击
 */
@Override
public void onItemChildClick(int position) {
    startPlay(position);
}
/**
     * 开始播放
     *
     * @param position 列表位置
     */
    protected void startPlay(int position) {
        if (mCurPos == position) return;
        if (mCurPos != -1) {
            releaseVideoView();
        }
        VideoEntity videoEntity = datas.get(position);
        //边播边存
//        String proxyUrl = ProxyVideoCacheManager.getProxy(getActivity()).getProxyUrl(videoBean.getUrl());
//        mVideoView.setUrl(proxyUrl);

        mVideoView.setUrl(videoEntity.getPlayurl());
        mTitleView.setTitle(videoEntity.getVtitle());
        View itemView = linearLayoutManager.findViewByPosition(position);
        if (itemView == null) return;
        VideoAdapter.ViewHolder viewHolder = (VideoAdapter.ViewHolder) itemView.getTag();
        //把列表中预置的PrepareView添加到控制器中,注意isPrivate此处只能为true。
        mController.addControlComponent(viewHolder.mPrepareView, true);
        Utils.removeViewFormParent(mVideoView);
        viewHolder.mPlayerContainer.addView(mVideoView, 0);
        //播放之前将VideoView添加到VideoViewManager以便在别的页面也能操作它
        getVideoViewManager().add(mVideoView, Tag.LIST);
        mVideoView.start();
        mCurPos = position;

    }

    private void releaseVideoView() {
        mVideoView.release();
        if (mVideoView.isFullScreen()) {
            mVideoView.stopFullScreen();
        }
        if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
        mCurPos = -1;
    }

实体类

Utils类

package com.ttit.myapp.util;

import android.view.View;
import android.view.ViewParent;
import android.widget.FrameLayout;

import com.dueeeke.videoplayer.player.VideoView;
import com.dueeeke.videoplayer.player.VideoViewConfig;
import com.dueeeke.videoplayer.player.VideoViewManager;

import java.lang.reflect.Field;

public final class Utils {

    private Utils() {
    }


    /**
     * 获取当前的播放核心
     */
    public static Object getCurrentPlayerFactory() {
        VideoViewConfig config = VideoViewManager.getConfig();
        Object playerFactory = null;
        try {
            Field mPlayerFactoryField = config.getClass().getDeclaredField("mPlayerFactory");
            mPlayerFactoryField.setAccessible(true);
            playerFactory = mPlayerFactoryField.get(config);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return playerFactory;
    }

    /**
     * 将View从父控件中移除
     */
    public static void removeViewFormParent(View v) {
        if (v == null) return;
        ViewParent parent = v.getParent();
        if (parent instanceof FrameLayout) {
            ((FrameLayout) parent).removeView(v);
        }
    }

    /**
     * Returns a string containing player state debugging information.
     */
    public static String playState2str(int state) {
        String playStateString;
        switch (state) {
            default:
            case VideoView.STATE_IDLE:
                playStateString = "idle";
                break;
            case VideoView.STATE_PREPARING:
                playStateString = "preparing";
                break;
            case VideoView.STATE_PREPARED:
                playStateString = "prepared";
                break;
            case VideoView.STATE_PLAYING:
                playStateString = "playing";
                break;
            case VideoView.STATE_PAUSED:
                playStateString = "pause";
                break;
            case VideoView.STATE_BUFFERING:
                playStateString = "buffering";
                break;
            case VideoView.STATE_BUFFERED:
                playStateString = "buffered";
                break;
            case VideoView.STATE_PLAYBACK_COMPLETED:
                playStateString = "playback completed";
                break;
            case VideoView.STATE_ERROR:
                playStateString = "error";
                break;
        }
        return String.format("playState: %s", playStateString);
    }

    /**
     * Returns a string containing player state debugging information.
     */
    public static String playerState2str(int state) {
        String playerStateString;
        switch (state) {
            default:
            case VideoView.PLAYER_NORMAL:
                playerStateString = "normal";
                break;
            case VideoView.PLAYER_FULL_SCREEN:
                playerStateString = "full screen";
                break;
            case VideoView.PLAYER_TINY_SCREEN:
                playerStateString = "tiny screen";
                break;
        }
        return String.format("playerState: %s", playerStateString);
    }


}

Tag类

package com.ttit.myapp.util;

/**
 * 播放器标签
 */
public final class Tag {
    //列表播放
    public static final String LIST = "list";
    //无缝播放
    public static final String SEAMLESS = "seamless";
    //画中画
    public static final String PIP = "pip";
}

item_video_layout.xml

item_video_layout.xml

<FrameLayout
    android:id="@+id/player_container"
    android:layout_width="match_parent"
    android:layout_height="187dp"
    android:layout_marginTop="8dp"
    android:background="@android:color/black"
    app:layout_constraintDimensionRatio="16:9"
    app:layout_constraintTop_toTopOf="parent">

    <com.dueeeke.videocontroller.component.PrepareView
        android:id="@+id/prepare_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>
xmlns:app="http://schemas.android.com/apk/res-auto"

VideoEntity

为它添加set,get()

private String playurl;

VideoAdapter

public class ViewHolder extends RecyclerView.ViewHolder {
mPrepareView = itemView.findViewById(R.id.prepare_view);
mThumb = itemView.findViewById(R.id.thumb);
mPlayerContainer = itemView.findViewById(R.id.player_container);

BaseFragment

protected VideoViewManager getVideoViewManager() {
    return VideoViewManager.instance();
}

listener文件夹

package com.ttit.myapp.listener;

public interface OnItemClickListener {
    void onItemClick(int position);
}
package com.ttit.myapp.listener;

public interface OnItemChildClickListener {
    void onItemChildClick(int position);
}

VideoAdapter

private OnItemChildClickListener mOnItemChildClickListener;

private OnItemClickListener mOnItemClickListener;

VideoFragment

public class VideoFragment extends BaseFragment implements OnItemChildClickListener  {

VideoAdapter

// 4 给视图对象赋值 1 当前对象 2 当前下标,作用:获取当前数据公共类对象
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    // 获取视图对象
    ViewHolder vh = (ViewHolder) holder; // 记得强转
    // 获取数据
    VideoEntity videoEntity = datas.get(position);
    // 设置数据
    vh.tvTitle.setText(videoEntity.getVtitle());
    vh.tvAuthor.setText(videoEntity.getAuthor());
    vh.tvDz.setText(String.valueOf(videoEntity.getLikenum()));// 需要转换为字符串
    vh.tvCollect.setText(String.valueOf(videoEntity.getCollectnum()));
    vh.tvComment.setText(String.valueOf(videoEntity.getCommentnum()));
    // 异步加载网络图片
    Picasso.with(mContext)
            .load(videoEntity.getHeadurl())
            .transform(new CircleTransform())
            .into(vh.imgHeader);
    Picasso.with(mContext).load(videoEntity.getCoverurl())
            .into(vh.mThumb);
    vh.mPosition = holder.getAdapterPosition();
}

// 2 设置需要加载模块的数量
@Override
public int getItemCount() {
    if (datas != null && datas.size() > 0) {
        return datas.size();// 根据传入数据,决定加载的数量。
    }
    return 0;
}

// 3 获取视图对象
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private TextView tvTitle;
    private TextView tvAuthor;
    private TextView tvDz;
    private TextView tvCollect;
    private TextView tvComment;
    // 获取图片资源
    private ImageView imgHeader;
    // 获取视频控件容器
    public ImageView mThumb;
    public PrepareView mPrepareView;
    public FrameLayout mPlayerContainer;
    public int mPosition;

    // 传入布局对象
    public ViewHolder(@NonNull View view) {
        super(view);
        tvTitle = view.findViewById(R.id.title);
        tvAuthor = view.findViewById(R.id.author);
        tvDz = view.findViewById(R.id.dz);
        tvCollect = view.findViewById(R.id.collect);
        tvComment = view.findViewById(R.id.comment);
        imgHeader = view.findViewById(R.id.img_header);
        mPrepareView = view.findViewById(R.id.prepare_view);
        mThumb = view.findViewById(R.id.thumb);
        mPlayerContainer = view.findViewById(R.id.player_container);
        mThumb = mPrepareView.findViewById(R.id.thumb);
        if (mOnItemChildClickListener != null) {
            mPlayerContainer.setOnClickListener(this);
        }
        if (mOnItemClickListener != null) {
            itemView.setOnClickListener(this);
        }
        //通过tag将ViewHolder和itemView绑定
        view.setTag(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.player_container) {
            if (mOnItemChildClickListener != null) {
                mOnItemChildClickListener.onItemChildClick(mPosition);
            }
        } else {
            if (mOnItemClickListener != null) {
                mOnItemClickListener.onItemClick(mPosition);
            }
        }
    }
}
public void setOnItemChildClickListener(OnItemChildClickListener onItemChildClickListener) {
    mOnItemChildClickListener = onItemChildClickListener;
}

public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
    mOnItemClickListener = onItemClickListener;
}

VideoFragment

@Override
    protected void initData() {
        linearLayoutManager = new LinearLayoutManager(getActivity());// 获取父活动对象
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);// 设置为垂直排列
        recyclerView.setLayoutManager(linearLayoutManager);// 为视图对象,设置布局管理器
        // 创建设配器
        videoAdapter = new VideoAdapter(getActivity());
        videoAdapter.setOnItemChildClickListener(this);
        // 添加布局到 主页列表控件
        recyclerView.setAdapter(videoAdapter);
        recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
            @Override
            public void onChildViewAttachedToWindow(@NonNull View view) {

            }

            @Override
            public void onChildViewDetachedFromWindow(@NonNull View view) {
                FrameLayout playerContainer = view.findViewById(R.id.player_container);
                View v = playerContainer.getChildAt(0);
                if (v != null && v == mVideoView && !mVideoView.isFullScreen()) {
                    releaseVideoView();
                }
            }
        });
        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(RefreshLayout refreshlayout) {// 过程:下拉刷新时,触发该方法
//                refreshlayout.finishRefresh(2000/*,false*/);//传入false表示刷新失败
                pageNum = 1; // 每次刷新 页数重置为1,就是回到首页
                getVideoList(true); // 传入数据
            }
        });
        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(RefreshLayout refreshlayout) {
//                refreshlayout.finishLoadMore(2000/*,false*/);//传入false表示加载失败
                pageNum ++; // 每加载一次,下一页
                getVideoList(false);// 更新传入的数据
            }
        });
        getVideoList(true);// 分页-2 第一次进来刷新
    }

视频屏幕适应

AndroidManifest.xml

<activity
    android:name=".activity.HomeActivity"
    android:exported="true"
    android:configChanges="orientation|screenSize"/>

测试运行

image-20220122161413034

image-20220122161427731

image-20220122161449608

视频分类

优化代码

/**
 * @author wuzhideren
 */
package com.ttit.myapp.fragment;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;

import com.flyco.tablayout.SlidingTabLayout;
import com.ttit.myapp.R;
import com.ttit.myapp.adapter.HomeAdapter;

import java.util.ArrayList;

public class HomeFragment extends BaseFragment {
    private ArrayList<Fragment> mFragments = new ArrayList<>();
    private final String[] mTitles = {
            "热门", "ios", "我的",
            "我的", "我的", "我的", "我的"
    };
    private ViewPager viewPager;
    private SlidingTabLayout slidingTabLayout;

    public static HomeFragment newInstance (){
        HomeFragment fragment = new HomeFragment();
        return fragment;
    }

    @Override
    protected int initLayout() {
        return R.layout.fragment_home;
    }

    @Override
    protected void initView() {
        viewPager = mRootView.findViewById(R.id.fixedViewPager);
        slidingTabLayout = mRootView.findViewById(R.id.slidingTabLayout);
    }

    @Override
    protected void initData() {
//根据导航栏标题数量创建frag,传入对应的导航栏标题
        for (String title : mTitles) {
            mFragments.add(VideoFragment.newInstance(title));
        }
        //解决闪退问题-预加载
        viewPager.setOffscreenPageLimit(mFragments.size());
        //2 为viewpager设置适配器,该适配器包含frag
        viewPager.setAdapter(new HomeAdapter(getFragmentManager(), mTitles, mFragments));
        //3 绑定导航栏和viewpager,实现滑动效果
        slidingTabLayout.setViewPager(viewPager);
    }
}

分类信息类

VideoCategoryResponse
package com.ttit.myapp.entity;

import java.io.Serializable;
import java.util.List;

/**
 * @author: wuzhideren
 * @date: 2022
 **/
public class VideoCategoryResponse implements Serializable {

    /**
     * msg : success
     * code : 0
     * page : {"totalCount":8,"pageSize":10,"totalPage":1,"currPage":1,"list":[{"categoryId":1,"categoryName":"游戏"},{"categoryId":2,"categoryName":"音乐"},{"categoryId":3,"categoryName":"美食"},{"categoryId":4,"categoryName":"农人"},{"categoryId":5,"categoryName":"vlog"},{"categoryId":6,"categoryName":"搞笑"},{"categoryId":7,"categoryName":"宠物"},{"categoryId":8,"categoryName":"军事"}]}
     */

    private String msg;
    private int code;
    private PageBean page;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

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

    public PageBean getPage() {
        return page;
    }

    public void setPage(PageBean page) {
        this.page = page;
    }

    public static class PageBean {
        /**
         * totalCount : 8
         * pageSize : 10
         * totalPage : 1
         * currPage : 1
         * list : [{"categoryId":1,"categoryName":"游戏"},{"categoryId":2,"categoryName":"音乐"},{"categoryId":3,"categoryName":"美食"},{"categoryId":4,"categoryName":"农人"},{"categoryId":5,"categoryName":"vlog"},{"categoryId":6,"categoryName":"搞笑"},{"categoryId":7,"categoryName":"宠物"},{"categoryId":8,"categoryName":"军事"}]
         */

        private int totalCount;
        private int pageSize;
        private int totalPage;
        private int currPage;
        private List<CategoryEntity> list;

        public int getTotalCount() {
            return totalCount;
        }

        public void setTotalCount(int totalCount) {
            this.totalCount = totalCount;
        }

        public int getPageSize() {
            return pageSize;
        }

        public void setPageSize(int pageSize) {
            this.pageSize = pageSize;
        }

        public int getTotalPage() {
            return totalPage;
        }

        public void setTotalPage(int totalPage) {
            this.totalPage = totalPage;
        }

        public int getCurrPage() {
            return currPage;
        }

        public void setCurrPage(int currPage) {
            this.currPage = currPage;
        }

        public List<CategoryEntity> getList() {
            return list;
        }

        public void setList(List<CategoryEntity> list) {
            this.list = list;
        }
    }
}
CategoryEntity
package com.ttit.myapp.entity;

import java.io.Serializable;

/**
 * @author: wuzhideren
 * @date: 2022
 **/
public class CategoryEntity implements Serializable {
    /**
     * categoryId : 1
     * categoryName : 游戏
     */

    private int categoryId;
    private String categoryName;

    public int getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(int categoryId) {
        this.categoryId = categoryId;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }
}

mongodb

1 下载 https://www.mongodb.com/try/download/community

2 创建文件夹mongodb,放入解压内容

3 在其内,创建文件夹:data、logs

4 在根目录路径命令行,输入

mongod --install --dbpath D:\300IT\mongodb\data --logpath D:\300IT\mongodb\logs\mongodb.log

5 回车

若无报错,则成功

成功:

image-20220122231300137

6 启动服务

net start mongodb

7 登录(验证是否安装成功)

mongo

易错 不是mongod

8 创建数据库且插入数据(复制粘贴办法在网上搜索)

image-20220122232635520

mongodb.txt

db.video.insertMany([{
   "vid" : NumberInt(1),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(2),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(3),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(4),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(5),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(6),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(7),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(8),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(9),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(10),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(11),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(12),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(13),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(14),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(15),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(16),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(17),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(18),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(19),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(20),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(21),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(22),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(23),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(24),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(25),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
},
{
   "vid" : NumberInt(26),
   "commentnum" : NumberInt(0),
   "likenum" : NumberInt(0),
   "collectnum" : NumberInt(0),
   "flagLike" : false,
   "flagCollect" : false
}])

实现分类标题

HomeFragment
/**
 * @author wuzhideren
 */
package com.ttit.myapp.fragment;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;

import com.flyco.tablayout.SlidingTabLayout;
import com.google.gson.Gson;
import com.ttit.myapp.R;
import com.ttit.myapp.activity.LoginActivity;
import com.ttit.myapp.adapter.HomeAdapter;
import com.ttit.myapp.api.Api;
import com.ttit.myapp.api.ApiConfig;
import com.ttit.myapp.api.TtitCallback;
import com.ttit.myapp.entity.CategoryEntity;
import com.ttit.myapp.entity.VideoCategoryResponse;
import com.ttit.myapp.entity.VideoEntity;
import com.ttit.myapp.entity.VideoListResponse;
import com.ttit.myapp.util.StringUtils;

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

public class HomeFragment extends BaseFragment {
    private ArrayList<Fragment> mFragments = new ArrayList<>();
    private String[] mTitles;
    private ViewPager viewPager;
    private SlidingTabLayout slidingTabLayout;

    public static HomeFragment newInstance (){
        HomeFragment fragment = new HomeFragment();
        return fragment;
    }

    @Override
    protected int initLayout() {
        return R.layout.fragment_home;
    }

    @Override
    protected void initView() {
        viewPager = mRootView.findViewById(R.id.fixedViewPager);
        slidingTabLayout = mRootView.findViewById(R.id.slidingTabLayout);
    }

    @Override
    protected void initData() {
        getVideoCategoryList();
    }

    //获取视频分类数据
    private void getVideoCategoryList() {//@isRefresh 分页-1 区分刷新还是下拉加载
        String token = getStringFromSp("token");
        //如果登录账户不为空
        if (!StringUtils.isEmpty(token)){
            HashMap<String, Object> params = new HashMap<>();//注意:是String,这是请求数据的容器
            params.put("token", token);
            Api.config(ApiConfig.VIDEO_CATEGORY_LIST, params).getRequest(new TtitCallback(){// 1 请求地址 2 请求参数

                @Override
                public void onSuccess(String res) {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            VideoCategoryResponse response = new Gson().fromJson(res, VideoCategoryResponse.class);
                            if (response != null && response.getCode() == 0) { // 如果返回数据正常
                                List<CategoryEntity> list = response.getPage().getList(); // 获取格式化后返回数据
                                if (list != null && list.size() > 0){ // 如果有数据
                                    mTitles = new String[list.size()];
                                    for (int i = 0; i < list.size(); i++) {
                                        mTitles[i] = list.get(i).getCategoryName();
                                        mFragments.add(VideoFragment.newInstance(list.get(i).getCategoryId()));
                                    }
                                    //解决闪退问题-预加载
                                    viewPager.setOffscreenPageLimit(mFragments.size());
                                    //2 为viewpager设置适配器,该适配器包含frag
                                    viewPager.setAdapter(new HomeAdapter(getFragmentManager(), mTitles, mFragments));
                                    //3 绑定导航栏和viewpager,实现滑动效果
                                    slidingTabLayout.setViewPager(viewPager);
                                }
                            }
                        }
                    });
                }

                @Override
                public void onFailure(Exception e) {
                }
            });
        }else {//说明没有登录
            navigateTo(LoginActivity.class);
        }
    }
}
VideoFragment
/**
 * @author wuzhideren
 */
package com.ttit.myapp.fragment;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
//import android.widget.VideoView; // 注意:别错包!!!

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

import com.dueeeke.videocontroller.StandardVideoController;
import com.dueeeke.videocontroller.component.CompleteView;
import com.dueeeke.videocontroller.component.ErrorView;
import com.dueeeke.videocontroller.component.GestureView;
import com.dueeeke.videocontroller.component.TitleView;
import com.dueeeke.videocontroller.component.VodControlView;
import com.dueeeke.videoplayer.player.VideoView;
import com.google.gson.Gson;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import com.ttit.myapp.R;
import com.ttit.myapp.activity.LoginActivity;
import com.ttit.myapp.adapter.VideoAdapter;
import com.ttit.myapp.api.Api;
import com.ttit.myapp.api.ApiConfig;
import com.ttit.myapp.api.TtitCallback;
import com.ttit.myapp.entity.VideoEntity;
import com.ttit.myapp.entity.VideoListResponse;
import com.ttit.myapp.listener.OnItemChildClickListener;
import com.ttit.myapp.listener.OnItemClickListener;
import com.ttit.myapp.util.StringUtils;
import com.ttit.myapp.util.Tag;
import com.ttit.myapp.util.Utils;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;

public class VideoFragment extends BaseFragment implements OnItemChildClickListener  {
    private int categoryId;//导航栏文字
    private RecyclerView recyclerView;
    private RefreshLayout refreshLayout;
    private int pageNum = 1; // 页数
    List<VideoEntity> datas = new ArrayList<>(); // 传入的数据
    VideoAdapter videoAdapter;
    protected VideoView mVideoView;
    protected StandardVideoController mController;
    protected ErrorView mErrorView;
    protected CompleteView mCompleteView;
    protected TitleView mTitleView;
    LinearLayoutManager linearLayoutManager;

    /**
     * 当前播放的位置
     */
    protected int mCurPos = -1;
    /**
     * 上次播放的位置,用于页面切回来之后恢复播放
     */
    protected int mLastPos = mCurPos;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    videoAdapter.setDatas(datas);
                    videoAdapter.notifyDataSetChanged();
                    break;
            }
        }
    };
    public static VideoFragment newInstance (int categoryId){
        VideoFragment fragment = new VideoFragment();
        //接收传入的title
        fragment.categoryId = categoryId;
        return fragment;
    }

    @Override
    protected int initLayout() {
        return R.layout.fragment_video;
    }

    @Override
    protected void initView() {
        initVideoView();// 找到视频视图
        recyclerView = mRootView.findViewById(R.id.recyclerView);
        refreshLayout = mRootView.findViewById(R.id.refreshLayout);
    }

    // 过程与onViewCreated一样
    @Override
    protected void initData() {
        linearLayoutManager = new LinearLayoutManager(getActivity());// 获取父活动对象
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);// 设置为垂直排列
        recyclerView.setLayoutManager(linearLayoutManager);// 为视图对象,设置布局管理器
        // 创建设配器
        videoAdapter = new VideoAdapter(getActivity());
        videoAdapter.setOnItemChildClickListener(this);
        // 添加布局到 主页列表控件
        recyclerView.setAdapter(videoAdapter);
        recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
            @Override
            public void onChildViewAttachedToWindow(@NonNull View view) {

            }

            @Override
            public void onChildViewDetachedFromWindow(@NonNull View view) {
                FrameLayout playerContainer = view.findViewById(R.id.player_container);
                View v = playerContainer.getChildAt(0);
                if (v != null && v == mVideoView && !mVideoView.isFullScreen()) {
                    releaseVideoView();
                }
            }
        });
        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(RefreshLayout refreshlayout) {// 过程:下拉刷新时,触发该方法
//                refreshlayout.finishRefresh(2000/*,false*/);//传入false表示刷新失败
                pageNum = 1; // 每次刷新 页数重置为1,就是回到首页
                getVideoList(true); // 传入数据
            }
        });
        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(RefreshLayout refreshlayout) {
//                refreshlayout.finishLoadMore(2000/*,false*/);//传入false表示加载失败
                pageNum ++; // 每加载一次,下一页
                getVideoList(false);// 更新传入的数据
            }
        });
        getVideoList(true);// 分页-2 第一次进来刷新
    }
    // 视频视图信息配置
    protected void initVideoView() {
        mVideoView = new VideoView(getActivity());
        mVideoView.setOnStateChangeListener(new com.dueeeke.videoplayer.player.VideoView.SimpleOnStateChangeListener() {
            @Override
            public void onPlayStateChanged(int playState) {
                //监听VideoViewManager释放,重置状态
                if (playState == com.dueeeke.videoplayer.player.VideoView.STATE_IDLE) {
                    Utils.removeViewFormParent(mVideoView);
                    mLastPos = mCurPos;
                    mCurPos = -1;
                }
            }
        });
        mController = new StandardVideoController(getActivity());
        mErrorView = new ErrorView(getActivity());
        mController.addControlComponent(mErrorView);
        mCompleteView = new CompleteView(getActivity());
        mController.addControlComponent(mCompleteView);
        mTitleView = new TitleView(getActivity());
        mController.addControlComponent(mTitleView);
        mController.addControlComponent(new VodControlView(getActivity()));
        mController.addControlComponent(new GestureView(getActivity()));
        mController.setEnableOrientation(true);
        mVideoView.setVideoController(mController);
    }

    @Override
    public void onPause() {
        super.onPause();
        pause();
    }

    /**
     * 由于onPause必须调用super。故增加此方法,
     * 子类将会重写此方法,改变onPause的逻辑
     */
    protected void pause() {
        releaseVideoView();
    }

    @Override
    public void onResume() {
        super.onResume();
        resume();
    }

    /**
     * 由于onResume必须调用super。故增加此方法,
     * 子类将会重写此方法,改变onResume的逻辑
     */
    protected void resume() {
        if (mLastPos == -1)
            return;
        //恢复上次播放的位置
        startPlay(mLastPos);
    }

    /**
     * PrepareView被点击
     */
    @Override
    public void onItemChildClick(int position) {
        startPlay(position);
    }

    /**
     * 开始播放
     *
     * @param position 列表位置
     */
    protected void startPlay(int position) {
        if (mCurPos == position) return;
        if (mCurPos != -1) {
            releaseVideoView();
        }
        VideoEntity videoEntity = datas.get(position);
        //边播边存
//        String proxyUrl = ProxyVideoCacheManager.getProxy(getActivity()).getProxyUrl(videoBean.getUrl());
//        mVideoView.setUrl(proxyUrl);

        mVideoView.setUrl(videoEntity.getPlayurl());
        mTitleView.setTitle(videoEntity.getVtitle());
        View itemView = linearLayoutManager.findViewByPosition(position);
        if (itemView == null) return;
        VideoAdapter.ViewHolder viewHolder = (VideoAdapter.ViewHolder) itemView.getTag();
        //把列表中预置的PrepareView添加到控制器中,注意isPrivate此处只能为true。
        mController.addControlComponent(viewHolder.mPrepareView, true);;//
        Utils.removeViewFormParent(mVideoView);
        viewHolder.mPlayerContainer.addView(mVideoView, 0);
        //播放之前将VideoView添加到VideoViewManager以便在别的页面也能操作它
        getVideoViewManager().add(mVideoView, Tag.LIST);
        mVideoView.start();
        mCurPos = position;

    }

    private void releaseVideoView() {
        mVideoView.release();
        if (mVideoView.isFullScreen()) {
            mVideoView.stopFullScreen();
        }
        if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
        mCurPos = -1;
    }

    //获取视频列表数据
    private void getVideoList(boolean isRefresh) {//@isRefresh 分页-1 区分刷新还是下拉加载
        String token = getStringFromSp("token");
        //如果登录账户不为空
        if (!StringUtils.isEmpty(token)){
            HashMap<String, Object> params = new HashMap<>();//注意:是String,这是请求数据的容器
            params.put("token", token);
            params.put("page", pageNum);// 总页数
            params.put("limit", ApiConfig.PAGE_SIZE);// 每页数量
            params.put("categoryId", categoryId);
            Api.config(ApiConfig.VIDEO_LIST_BY_CATEGORY, params).getRequest(new TtitCallback(){
            @Override
            public void onSuccess(String res) {
                if (isRefresh){// 请求成功就关闭加载动画
                    refreshLayout.finishRefresh(true);
                }else {
                    refreshLayout.finishLoadMore(true);// 注意!别和上面写成一样了
                }
                VideoListResponse response = new Gson().fromJson(res, VideoListResponse.class);
                if (response != null && response.getCode() == 0) { // 如果返回数据正常
                    List<VideoEntity> list = response.getPage().getList(); // 获取格式化后返回数据
                    if (list != null && list.size() > 0){ // 如果有数据
                        if (isRefresh){// 如果时刷新
                            datas = list;
                        } else { // 如果加载
                            datas.addAll(list); // 追加加载后的数据
                        }
                        mHandler.sendEmptyMessage(0);
                    }else {
                        if (isRefresh){
//                                        showToastSync();// 注意!不能使用
                            showToastSync("暂时无数据");
                        }else {
                            showToastSync("没有更多数据");
                        }
                    }
                }
            }

                @Override
            public void onFailure(Exception e) {
                if (isRefresh){
                    refreshLayout.finishRefresh(true);
                }else {
                    refreshLayout.finishLoadMore(true);
                }
            }
        });
        }else {//说明没有登录
            navigateTo(LoginActivity.class);
        }
    }
}
处理子线程UI

优化代码

 private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    videoAdapter.setDatas(datas);
                    videoAdapter.notifyDataSetChanged();
                    break;
            }
        }
    };
//....此处省略
@Override
public void onSuccess(String res) {
    if (isRefresh){// 请求成功就关闭加载动画
        refreshLayout.finishRefresh(true);
    }else {
        refreshLayout.finishLoadMore(true);// 注意!别和上面写成一样了
    }
    VideoListResponse response = new Gson().fromJson(res, VideoListResponse.class);
    if (response != null && response.getCode() == 0) { // 如果返回数据正常
        List<VideoEntity> list = response.getPage().getList(); // 获取格式化后返回数据
        if (list != null && list.size() > 0){ // 如果有数据
            if (isRefresh){// 如果时刷新
                datas = list;
            } else { // 如果加载
                datas.addAll(list); // 追加加载后的数据
            }
            mHandler.sendEmptyMessage(0);
        }else {
            if (isRefresh){
                //                                        showToastSync();// 注意!不能使用
                showToastSync("暂时无数据");
            }else {
                showToastSync("没有更多数据");
            }
        }
    }
}
测试运行

image-20220122234509406

完整的笔记我已经在本地完成

在这里插入图片描述
如果没有人需要,后续不会再更新了

如果要转载,务必同时把参考资料源处转载

视频地址

  • https://www.bilibili.com/video/BV16Z4y1H7jj?spm_id_from=333.999.0.0
  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值