0
点赞
收藏
分享

微信扫一扫

安卓App(视频资讯)开发保姆级视频 详细+避错+兼容+注释(一)

诗远 2022-01-26 阅读 18

开始开发时间2022.1月

基于视频教程开发
视频教程地址:https://www.bilibili.com/video/BV16Z4y1H7jj?spm_id_from=333.999.0.0
视频下方有源码

这个笔记将会解决后端数据问题、解决数据库版本等等问题。如果没有人需要,就不更新了,在csdn写笔记挺麻烦的。

UI界面私人定制

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

环境搭建

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>

尺寸配置文件

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

在这里插入图片描述

<?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

布局文件

<?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文件下

矩形按钮

<?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>

在这里插入图片描述

无状态条

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

运行图片

在这里插入图片描述

登录页面UI

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

布局文件

<?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>

阴影边框

<?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的限制:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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>

在这里插入图片描述

// 原因:在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);
            }
        });
    }
}

存储逻辑类

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();//易错修正:其后代码不会立即执行
    }

测试运行

举报

相关推荐

0 条评论