Commit ce422dbf by weijiguang

init

parents
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
/.idea/
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.ihaoin.hooloo.device"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation project(path: ':library_recyclerview')
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.kyleduo.switchbutton:library:2.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'com.squareup.okhttp3:okhttp:3.5.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
compileOnly 'org.projectlombok:lombok:1.18.24'
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ihaoin.hooloo.device"
android:versionCode="1"
android:versionName="1.0">
<application
android:name=".HLApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".view.LauncherActivity"
android:label="@string/app_name"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY" />
</intent-filter>
</activity>
</application>
</manifest>
\ No newline at end of file
package com.ihaoin.hooloo.device;
import android.app.Application;
public class HLApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onTerminate() {
super.onTerminate();
}
}
package com.ihaoin.hooloo.device.data;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
@Data
public class Category implements Serializable {
/** 分类id */
private Integer id;
/** 分类名称 */
private String name;
/** 商品信息 */
private List<Goods> goods;
}
package com.ihaoin.hooloo.device.data;
import java.util.List;
import java.util.Map;
import lombok.Data;
@Data
public class Datas {
private Integer countOfOrder = 9;
private String tips = "http://www.baidu.com";
private List<String> leftImages;
private Map<Integer, String> rightImages;
private List<Recommend> recommends;
private List<Category> categorys;
}
package com.ihaoin.hooloo.device.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
import lombok.Data;
@Data
public class Goods implements Serializable {
/** 商品id */
private Integer goodId;
/** 名称 */
private String name;
/** 原价 */
private BigDecimal price;
/** 折扣价 */
private BigDecimal discount;
/** 介绍 */
private String desc;
/** 备注 */
private String remark;
/** 图片信息 */
private Pics pics;
/** 规格 */
private List<GoodsSpec> specs;
/** 标签 */
private List<String> tags;
/** SKU */
private List<Sku> skus;
}
package com.ihaoin.hooloo.device.data;
import java.math.BigDecimal;
import java.util.List;
import lombok.Data;
@Data
public class GoodsRule {
/** 选项id */
private Integer ruleId;
/** 名称 */
private String ruleName;
/** 价格 */
private BigDecimal price;
}
package com.ihaoin.hooloo.device.data;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
@Data
public class GoodsSpec implements Serializable {
/** 规格id */
private Integer specId;
/** 名称 */
private String specName;
/** 选项列表 */
private List<GoodsRule> rules;
}
package com.ihaoin.hooloo.device.data;
import java.util.List;
import java.util.Map;
import lombok.Data;
@Data
public class Pics {
/** 缩略图 */
private String thumbnail;
/** 介绍图 */
private List<String> introImages;
/** 详情图 */
private List<String> detailImages;
}
package com.ihaoin.hooloo.device.data;
import java.io.Serializable;
import lombok.Data;
@Data
public class Recommend implements Serializable {
/** 推荐标题 */
private String title;
/** 推荐介绍 */
private String desc;
/** 推荐背景图 */
private String background;
/** 商品信息 */
private Goods goods;
}
package com.ihaoin.hooloo.device.data;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
@Data
public class Sku implements Serializable {
/** sku id */
private Integer skuId;
/** 状态:0-售罄,1-可售 */
private Integer state;
/** SKU规格 */
private List<SkuRule> rules;
}
package com.ihaoin.hooloo.device.data;
import java.math.BigDecimal;
import lombok.Data;
@Data
public class SkuRule {
/** 规格id */
private Integer specId;
/** 规格名称 */
private String specName;
/** 选项id */
private Integer ruleId;
/** 选项名称 */
private String ruleName;
/** 价格 */
private BigDecimal price;
}
package com.ihaoin.hooloo.device.util;
public class Utils {
}
package com.ihaoin.hooloo.device.view;
import android.app.Activity;
import android.os.Bundle;
import android.view.WindowManager;
import com.ihaoin.hooloo.device.R;
import java.math.BigDecimal;
public class LauncherActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_launcher);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
<?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:orientation="horizontal"
tools:context=".view.LauncherActivity">
<include
layout="@layout/view_trolley"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/layout_recommends"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="7"
android:orientation="vertical" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rec_spu"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/right_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:singleLine="true"
android:textSize="14sp" />
</FrameLayout>
<View
android:layout_width="0.5dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rec_category"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="10dp">
<ToggleButton
android:id="@+id/butn_checked"
android:layout_width="30dp"
android:layout_height="30dp" />
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/txt_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="冰茶咖啡"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txt_options"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="冰/无糖"
android:textColor="@android:color/darker_gray"
android:textSize="12sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/txt_original_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ms__arrow"
android:gravity="center_vertical"
android:text="19.9"
android:textColor="@android:color/black"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txt_discount_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:drawableLeft="@drawable/ms__arrow"
android:gravity="center_vertical"
android:text="29.9"
android:textColor="@android:color/darker_gray"
android:textSize="9sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/butn_subtract"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/ms__arrow" />
<TextView
android:id="@+id/txt_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text="1"
android:textColor="@android:color/black"
android:textSize="12sp" />
<Button
android:id="@+id/butn_add"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/ms__menu_down" />
</LinearLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="确认订单"
android:textColor="@android:color/black"
android:textSize="24sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/img_qrcode"
android:layout_width="300dp"
android:layout_height="300dp"
android:src="@mipmap/qrcode" />
<LinearLayout
android:id="@+id/layout_wait_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/txt_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="使用微信扫一扫,确认订单并付款"
android:textColor="@android:color/black"
android:textSize="20sp" />
<TextView
android:id="@+id/txt_times"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="60秒后失效"
android:textColor="@android:color/black"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_scan_succeed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:visibility="gone">
<ImageView
android:id="@+id/img_succeed"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@mipmap/qrcode" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="扫码成功"
android:textColor="@android:color/black"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="在微信小程序上确认订单并付款"
android:textColor="@android:color/black"
android:textSize="20sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/butn_close"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_margin="20dp"
android:src="@drawable/ms__arrow" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="@+id/img_intro"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:adjustViewBounds="true"
android:src="@mipmap/wx20220427" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/txt_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="冰茶咖啡"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/layout_categorys"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<LinearLayout
android:id="@+id/layout_images"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@android:color/darker_gray"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/txt_original_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ms__arrow"
android:gravity="center_vertical"
android:text="19.9"
android:textColor="@android:color/black"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txt_discount_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:drawableLeft="@drawable/ms__arrow"
android:gravity="center_vertical"
android:text="29.9"
android:textColor="@android:color/darker_gray"
android:textSize="9sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/butn_subtract"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/ms__arrow" />
<TextView
android:id="@+id/txt_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text="1"
android:textColor="@android:color/black"
android:textSize="12sp" />
<Button
android:id="@+id/butn_add"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/ms__menu_down" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/butn_buy"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:text="立即购买" />
<Button
android:id="@+id/butn_trolley"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:text="加入购物袋" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/butn_close"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/ms__arrow"
android:layout_alignParentRight="true"
android:layout_margin="20dp"/>
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/layout_trolley"
android:layout_width="400dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@android:color/holo_red_dark"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:text="购物袋"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<Button
android:id="@+id/butn_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@null"
android:drawableLeft="@drawable/ms__menu_down"
android:gravity="right|center_vertical"
android:text="清空购物车"
android:textColor="@android:color/darker_gray"
android:textSize="12sp" />
</LinearLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@android:color/darker_gray" />
<ListView
android:id="@+id/list_trolley"
android:layout_width="match_parent"
android:layout_height="300dp"
android:divider="@null" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_settle_bar"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="60dp"
android:layout_height="match_parent">
<ImageView
android:id="@+id/img_trolley"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:src="@color/colorPrimaryDark" />
<TextView
android:id="@+id/txt_count"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentRight="true"
android:background="@android:color/holo_red_dark"
android:gravity="center"
android:text="9"
android:textSize="12sp" />
</RelativeLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/txt_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ms__menu_down"
android:text="10"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txt_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="冰茶咖啡(冰)(全糖)x1 冰茶咖啡(冰)(无糖)x1"
android:textColor="@android:color/darker_gray"
android:textSize="12sp" />
</LinearLayout>
<Button
android:id="@+id/butn_pay"
android:layout_width="100dp"
android:layout_height="match_parent"
android:background="@android:color/black"
android:text="付款"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
</RelativeLayout>
\ No newline at end of file
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.ihaoin.hooloo.device.view.LauncherActivity">
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
app:showAsAction="never" />
</menu>
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>
<resources>
<dimen name="fab_margin">16dp</dimen>
</resources>
<resources>
<string name="app_name">hooloo</string>
<string name="action_settings">Settings</string>
</resources>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.5.31'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
android.enableJetifier=true
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
#android.buildCacheDir=./build/buildCache/
android.useAndroidX=true
android.enableD8=true
#android.enableBuildCache=true
#Sun Apr 24 15:23:27 CST 2022
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
.gradle/
.DS_Store
local.properties
# build files
build/
bin/
gen/
output/
# android studio
*.iml
.idea
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
//apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 14
targetSdkVersion 30
}
buildTypes {
release {
consumerProguardFiles 'proguard-rules.pro'
}
debug {
consumerProguardFiles 'proguard-rules.pro'
}
}
compileOptions {
kotlinOptions.freeCompilerArgs += "-module-name"
kotlinOptions.freeCompilerArgs += "com.github.CymChad.brvah"
}
lintOptions {
abortOnError false
}
}
// 打包源码jar
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
archiveClassifier = 'sources'
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.annotation:annotation:1.2.0'
implementation 'androidx.databinding:library:3.2.0-alpha11'
implementation 'androidx.databinding:viewbinding:4.2.2'
compileOnly 'androidx.recyclerview:recyclerview:1.2.1'
}
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/huasheng/Desktop/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
#-keep class com.chad.library.adapter.** {
#*;
#}
#-keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter
-keep public class * extends com.chad.library.adapter.base.viewholder.BaseViewHolder
-keepclassmembers class **$** extends com.chad.library.adapter.base.viewholder.BaseViewHolder {
<init>(...);
}
#-keepattributes InnerClasses
#
#-keep class androidx.** {*;}
#-keep public class * extends androidx.**
#-keep interface androidx.** {*;}
#-dontwarn androidx.**
\ No newline at end of file
<manifest package="com.chad.library"
xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
package com.chad.library.adapter.base
import android.annotation.SuppressLint
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.chad.library.adapter.base.binder.BaseItemBinder
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
* 使用 Binder 来实现adapter,既可以实现单布局,也能实现多布局
* 数据实体类也不存继承问题
*
* 当有多种条目的时候,避免在convert()中做太多的业务逻辑,把逻辑放在对应的 BaseItemBinder 中。
* 适用于以下情况:
* 1、实体类不方便扩展,此Adapter的数据类型可以是任意类型,默认情况下不需要实现 getItemType
* 2、item 类型较多,在convert()中管理起来复杂
*
* ViewHolder 由 [BaseItemBinder] 实现,并且每个[BaseItemBinder]可以拥有自己类型的ViewHolder类型。
*
* 数据类型为Any
*/
open class BaseBinderAdapter(list: MutableList<Any>? = null) : BaseQuickAdapter<Any, BaseViewHolder>(0, list) {
/**
* 用于存储每个 Binder 类型对应的 Diff
*/
private val classDiffMap = HashMap<Class<*>, DiffUtil.ItemCallback<Any>?>()
private val mTypeMap = HashMap<Class<*>, Int>()
private val mBinderArray = SparseArray<BaseItemBinder<Any, *>>()
init {
setDiffCallback(ItemCallback())
}
/**
* 添加 ItemBinder
*/
@JvmOverloads
fun <T : Any> addItemBinder(clazz: Class<out T>, baseItemBinder: BaseItemBinder<T, *>, callback: DiffUtil.ItemCallback<T>? = null): BaseBinderAdapter {
val itemType = mTypeMap.size + 1
mTypeMap[clazz] = itemType
mBinderArray.append(itemType, baseItemBinder as BaseItemBinder<Any, *>)
baseItemBinder._adapter = this
callback?.let {
classDiffMap[clazz] = it as DiffUtil.ItemCallback<Any>
}
return this
}
/**
* kotlin 可以使用如下方法添加 ItemBinder,更加简单
*/
inline fun <reified T : Any> addItemBinder(baseItemBinder: BaseItemBinder<T, *>, callback: DiffUtil.ItemCallback<T>? = null): BaseBinderAdapter {
addItemBinder(T::class.java, baseItemBinder, callback)
return this
}
override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return getItemBinder(viewType).let {
it._context = context
it.onCreateViewHolder(parent, viewType)
}
}
override fun convert(holder: BaseViewHolder, item: Any) {
getItemBinder(holder.itemViewType).convert(holder, item)
}
override fun convert(holder: BaseViewHolder, item: Any, payloads: List<Any>) {
getItemBinder(holder.itemViewType).convert(holder, item, payloads)
}
open fun getItemBinder(viewType: Int): BaseItemBinder<Any, BaseViewHolder> {
val binder = mBinderArray[viewType]
checkNotNull(binder) { "getItemBinder: viewType '$viewType' no such Binder found,please use addItemBinder() first!" }
return binder as BaseItemBinder<Any, BaseViewHolder>
}
open fun getItemBinderOrNull(viewType: Int): BaseItemBinder<Any, BaseViewHolder>? {
val binder = mBinderArray[viewType]
return binder as? BaseItemBinder<Any, BaseViewHolder>
}
override fun getDefItemViewType(position: Int): Int {
return findViewType(data[position].javaClass)
}
override fun bindViewClickListener(viewHolder: BaseViewHolder, viewType: Int) {
super.bindViewClickListener(viewHolder, viewType)
bindClick(viewHolder)
bindChildClick(viewHolder, viewType)
}
override fun onViewAttachedToWindow(holder: BaseViewHolder) {
super.onViewAttachedToWindow(holder)
getItemBinderOrNull(holder.itemViewType)?.onViewAttachedToWindow(holder)
}
override fun onViewDetachedFromWindow(holder: BaseViewHolder) {
super.onViewDetachedFromWindow(holder)
getItemBinderOrNull(holder.itemViewType)?.onViewDetachedFromWindow(holder)
}
override fun onFailedToRecycleView(holder: BaseViewHolder): Boolean {
return getItemBinderOrNull(holder.itemViewType)?.onFailedToRecycleView(holder) ?: false
}
protected fun findViewType(clazz : Class<*>):Int {
val type = mTypeMap[clazz]
checkNotNull(type) { "findViewType: ViewType: $clazz Not Find!" }
return type
}
protected open fun bindClick(viewHolder: BaseViewHolder) {
if (getOnItemClickListener() == null) {
//如果没有设置点击监听,则回调给 itemProvider
//Callback to itemProvider if no click listener is set
viewHolder.itemView.setOnClickListener {
var position = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnClickListener
}
position -= headerLayoutCount
val itemViewType = viewHolder.itemViewType
val binder = getItemBinder(itemViewType)
binder.onClick(viewHolder, it, data[position], position)
}
}
if (getOnItemLongClickListener() == null) {
//如果没有设置长按监听,则回调给itemProvider
// If you do not set a long press listener, callback to the itemProvider
viewHolder.itemView.setOnLongClickListener {
var position = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnLongClickListener false
}
position -= headerLayoutCount
val itemViewType = viewHolder.itemViewType
val binder = getItemBinder(itemViewType)
binder.onLongClick(viewHolder, it, data[position], position)
}
}
}
protected open fun bindChildClick(viewHolder: BaseViewHolder, viewType: Int) {
if (getOnItemChildClickListener() == null) {
val provider = getItemBinder(viewType)
val ids = provider.getChildClickViewIds()
ids.forEach { id ->
viewHolder.itemView.findViewById<View>(id)?.let {
if (!it.isClickable) {
it.isClickable = true
}
it.setOnClickListener { v ->
var position: Int = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnClickListener
}
position -= headerLayoutCount
provider.onChildClick(viewHolder, v, data[position], position)
}
}
}
}
if (getOnItemChildLongClickListener() == null) {
val provider = getItemBinder(viewType)
val ids = provider.getChildLongClickViewIds()
ids.forEach { id ->
viewHolder.itemView.findViewById<View>(id)?.let {
if (!it.isLongClickable) {
it.isLongClickable = true
}
it.setOnLongClickListener { v ->
var position: Int = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnLongClickListener false
}
position -= headerLayoutCount
provider.onChildLongClick(viewHolder, v, data[position], position)
}
}
}
}
}
/**
* Diff Callback
*/
private inner class ItemCallback : DiffUtil.ItemCallback<Any>() {
override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean {
if (oldItem.javaClass == newItem.javaClass) {
classDiffMap[oldItem.javaClass]?.let {
return it.areItemsTheSame(oldItem, newItem)
}
}
return oldItem == newItem
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {
if (oldItem.javaClass == newItem.javaClass) {
classDiffMap[oldItem.javaClass]?.let {
return it.areContentsTheSame(oldItem, newItem)
}
}
return true
}
override fun getChangePayload(oldItem: Any, newItem: Any): Any? {
if (oldItem.javaClass == newItem.javaClass) {
return classDiffMap[oldItem.javaClass]?.getChangePayload(oldItem, newItem)
}
return null
}
}
}
\ No newline at end of file
package com.chad.library.adapter.base
import android.view.ViewGroup
import com.chad.library.adapter.base.delegate.BaseMultiTypeDelegate
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
* 多类型布局,通过代理类的方式,返回布局 id 和 item 类型;
* 适用于:
* 1、实体类不方便扩展,此Adapter的数据类型可以是任意类型,只需要在[BaseMultiTypeDelegate.getItemType]中返回对应类型
* 2、item 类型较少
* 如果类型较多,为了方便隔离各类型的业务逻辑,推荐使用[BaseBinderAdapter]
*
* @param T
* @param VH : BaseViewHolder
* @property mMultiTypeDelegate BaseMultiTypeDelegate<T>?
* @constructor
*/
abstract class BaseDelegateMultiAdapter<T, VH : BaseViewHolder>(data: MutableList<T>? = null) :
BaseQuickAdapter<T, VH>(0, data) {
private var mMultiTypeDelegate: BaseMultiTypeDelegate<T>? = null
/**
* 通过此方法设置代理
* @param multiTypeDelegate BaseMultiTypeDelegate<T>
*/
fun setMultiTypeDelegate(multiTypeDelegate: BaseMultiTypeDelegate<T>) {
this.mMultiTypeDelegate = multiTypeDelegate
}
fun getMultiTypeDelegate(): BaseMultiTypeDelegate<T>? = mMultiTypeDelegate
override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VH {
val delegate = getMultiTypeDelegate()
checkNotNull(delegate) { "Please use setMultiTypeDelegate first!" }
val layoutId = delegate.getLayoutId(viewType)
return createBaseViewHolder(parent, layoutId)
}
override fun getDefItemViewType(position: Int): Int {
val delegate = getMultiTypeDelegate()
checkNotNull(delegate) { "Please use setMultiTypeDelegate first!" }
return delegate.getItemType(data, position)
}
}
\ No newline at end of file
package com.chad.library.adapter.base
import android.util.SparseIntArray
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import com.chad.library.adapter.base.entity.MultiItemEntity
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
* 多类型布局,适用于类型较少,业务不复杂的场景,便于快速使用。
* data[T]必须实现[MultiItemEntity]
*
* 如果数据类无法实现[MultiItemEntity],请使用[BaseDelegateMultiAdapter]
* 如果类型较多,为了方便隔离各类型的业务逻辑,推荐使用[BaseProviderMultiAdapter]
*
* @param T : MultiItemEntity
* @param VH : BaseViewHolder
* @constructor
*/
abstract class BaseMultiItemQuickAdapter<T : MultiItemEntity, VH : BaseViewHolder>(data: MutableList<T>? = null)
: BaseQuickAdapter<T, VH>(0, data) {
private val layouts: SparseIntArray by lazy(LazyThreadSafetyMode.NONE) { SparseIntArray() }
override fun getDefItemViewType(position: Int): Int {
return data[position].itemType
}
override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VH {
val layoutResId = layouts.get(viewType)
require(layoutResId != 0) { "ViewType: $viewType found layoutResId,please use addItemType() first!" }
return createBaseViewHolder(parent, layoutResId)
}
/**
* 调用此方法,设置多布局
* @param type Int
* @param layoutResId Int
*/
protected fun addItemType(type: Int, @LayoutRes layoutResId: Int) {
layouts.put(type, layoutResId)
}
}
\ No newline at end of file
package com.chad.library.adapter.base
import androidx.annotation.IntRange
import androidx.recyclerview.widget.DiffUtil
import com.chad.library.adapter.base.entity.node.BaseExpandNode
import com.chad.library.adapter.base.entity.node.BaseNode
import com.chad.library.adapter.base.entity.node.NodeFooterImp
import com.chad.library.adapter.base.provider.BaseItemProvider
import com.chad.library.adapter.base.provider.BaseNodeProvider
abstract class BaseNodeAdapter(nodeList: MutableList<BaseNode>? = null)
: BaseProviderMultiAdapter<BaseNode>(null) {
private val fullSpanNodeTypeSet = HashSet<Int>()
init {
if (!nodeList.isNullOrEmpty()) {
val flatData = flatData(nodeList)
this.data.addAll(flatData)
}
}
/**
* 添加 node provider
* @param provider BaseItemProvider
*/
fun addNodeProvider(provider: BaseNodeProvider) {
addItemProvider(provider)
}
/**
* 添加需要铺满的 node provider
* @param provider BaseItemProvider
*/
fun addFullSpanNodeProvider(provider: BaseNodeProvider) {
fullSpanNodeTypeSet.add(provider.itemViewType)
addItemProvider(provider)
}
/**
* 添加脚部 node provider
* 铺满一行或者一列
* @param provider BaseItemProvider
*/
fun addFooterNodeProvider(provider: BaseNodeProvider) {
addFullSpanNodeProvider(provider)
}
/**
* 请勿直接通过此方法添加 node provider!
* @param provider BaseItemProvider<BaseNode, VH>
*/
override fun addItemProvider(provider: BaseItemProvider<BaseNode>) {
if (provider is BaseNodeProvider) {
super.addItemProvider(provider)
} else {
throw IllegalStateException("Please add BaseNodeProvider, no BaseItemProvider!")
}
}
override fun isFixedViewType(type: Int): Boolean {
return super.isFixedViewType(type) || fullSpanNodeTypeSet.contains(type)
}
override fun setNewInstance(list: MutableList<BaseNode>?) {
super.setNewInstance(flatData(list ?: arrayListOf()))
}
/**
* 替换整个列表数据,如果需要对某节点下的子节点进行替换,请使用[nodeReplaceChildData]!
*/
override fun setList(list: Collection<BaseNode>?) {
super.setList(flatData(list ?: arrayListOf()))
}
/**
* 如果需要对某节点下的子节点进行数据操作,请使用[nodeAddData]!
*
* @param position Int 整个 data 的 index
* @param data BaseNode
*/
override fun addData(position: Int, data: BaseNode) {
addData(position, arrayListOf(data))
}
override fun addData(data: BaseNode) {
addData(arrayListOf(data))
}
override fun addData(position: Int, newData: Collection<BaseNode>) {
val nodes = flatData(newData)
super.addData(position, nodes)
}
override fun addData(newData: Collection<BaseNode>) {
val nodes = flatData(newData)
super.addData(nodes)
}
/**
* 如果需要对某节点下的子节点进行数据操作,请使用[nodeRemoveData]!
*
* @param position Int 整个 data 的 index
*/
override fun removeAt(position: Int) {
val removeCount = removeNodesAt(position)
notifyItemRangeRemoved(position + headerLayoutCount, removeCount)
compatibilityDataSizeChanged(0)
}
/**
* 如果需要对某节点下的子节点进行数据操作,请使用[nodeSetData]!
* @param index Int
* @param data BaseNode
*/
override fun setData(index: Int, data: BaseNode) {
// 先移除,再添加
val removeCount = removeNodesAt(index)
val newFlatData = flatData(arrayListOf(data))
this.data.addAll(index, newFlatData)
if (removeCount == newFlatData.size) {
notifyItemRangeChanged(index + headerLayoutCount, removeCount)
} else {
notifyItemRangeRemoved(index + headerLayoutCount, removeCount)
notifyItemRangeInserted(index + headerLayoutCount, newFlatData.size)
// notifyItemRangeChanged(index + getHeaderLayoutCount(), max(removeCount, newFlatData.size)
}
}
override fun setDiffNewData(list: MutableList<BaseNode>?, commitCallback: Runnable?) {
if (hasEmptyView()) {
setNewInstance(list)
return
}
super.setDiffNewData(flatData(list ?: arrayListOf()), commitCallback)
}
override fun setDiffNewData(diffResult: DiffUtil.DiffResult, list: MutableList<BaseNode>) {
if (hasEmptyView()) {
setNewInstance(list)
return
}
super.setDiffNewData(diffResult, flatData(list))
}
/**
* 从数组中移除
* @param position Int
* @return Int 被移除的数量
*/
private fun removeNodesAt(position: Int): Int {
if (position >= data.size) {
return 0
}
// 记录被移除的item数量
var removeCount = 0
// 先移除子项
removeCount = removeChildAt(position)
// 移除node自己
this.data.removeAt(position)
removeCount += 1
val node = this.data[position]
// 移除脚部
if (node is NodeFooterImp && node.footerNode != null) {
this.data.removeAt(position)
removeCount += 1
}
return removeCount
}
private fun removeChildAt(position: Int): Int {
if (position >= data.size) {
return 0
}
// 记录被移除的item数量
var removeCount = 0
val node = this.data[position]
// 移除子项
if (!node.childNode.isNullOrEmpty()) {
if (node is BaseExpandNode) {
if (node.isExpanded) {
val items = flatData(node.childNode!!)
this.data.removeAll(items)
removeCount = items.size
}
} else {
val items = flatData(node.childNode!!)
this.data.removeAll(items)
removeCount = items.size
}
}
return removeCount
}
/*************************** 重写数据设置方法 END ***************************/
/*************************** Node 数据操作 ***************************/
/**
* 对指定的父node,添加子node
* @param parentNode BaseNode 父node
* @param data BaseNode 子node
*/
fun nodeAddData(parentNode: BaseNode, data: BaseNode) {
parentNode.childNode?.let {
it.add(data)
if (parentNode is BaseExpandNode && !parentNode.isExpanded) {
return
}
val parentIndex = this.data.indexOf(parentNode)
val childIndex = it.size
addData(parentIndex + childIndex, data)
}
}
/**
* 对指定的父node,在指定位置添加子node
* @param parentNode BaseNode 父node
* @param childIndex Int 此位置是相对于其childNodes数据的位置!并不是整个data
* @param data BaseNode 添加的数据
*/
fun nodeAddData(parentNode: BaseNode, childIndex: Int, data: BaseNode) {
parentNode.childNode?.let {
it.add(childIndex, data)
if (parentNode is BaseExpandNode && !parentNode.isExpanded) {
return
}
val parentIndex = this.data.indexOf(parentNode)
val pos = parentIndex + 1 + childIndex
addData(pos, data)
}
}
/**
* 对指定的父node,在指定位置添加子node集合
* @param parentNode BaseNode 父node
* @param childIndex Int 此位置是相对于其childNodes数据的位置!并不是整个data
* @param newData 添加的数据集合
*/
fun nodeAddData(parentNode: BaseNode, childIndex: Int, newData: Collection<BaseNode>) {
parentNode.childNode?.let {
it.addAll(childIndex, newData)
if (parentNode is BaseExpandNode && !parentNode.isExpanded) {
return
}
val parentIndex = this.data.indexOf(parentNode)
val pos = parentIndex + 1 + childIndex
addData(pos, newData)
}
}
/**
* 对指定的父node下对子node进行移除
* @param parentNode BaseNode 父node
* @param childIndex Int 此位置是相对于其childNodes数据的位置!并不是整个data
*/
fun nodeRemoveData(parentNode: BaseNode, childIndex: Int) {
parentNode.childNode?.let {
if (childIndex >= it.size) {
return
}
if (parentNode is BaseExpandNode && !parentNode.isExpanded) {
it.removeAt(childIndex)
return
}
val parentIndex = this.data.indexOf(parentNode)
val pos = parentIndex + 1 + childIndex
remove(pos)
it.removeAt(childIndex)
}
}
/**
* 对指定的父node下对子node进行移除
* @param parentNode BaseNode 父node
* @param childNode BaseNode 子node
*/
fun nodeRemoveData(parentNode: BaseNode, childNode: BaseNode) {
parentNode.childNode?.let {
if (parentNode is BaseExpandNode && !parentNode.isExpanded) {
it.remove(childNode)
return
}
remove(childNode)
it.remove(childNode)
}
}
/**
* 改变指定的父node下的子node数据
* @param parentNode BaseNode
* @param childIndex Int 此位置是相对于其childNodes数据的位置!并不是整个data
* @param data BaseNode 新数据
*/
fun nodeSetData(parentNode: BaseNode, childIndex: Int, data: BaseNode) {
parentNode.childNode?.let {
if (childIndex >= it.size) {
return
}
if (parentNode is BaseExpandNode && !parentNode.isExpanded) {
it[childIndex] = data
return
}
val parentIndex = this.data.indexOf(parentNode)
val pos = parentIndex + 1 + childIndex
setData(pos, data)
it[childIndex] = data
}
}
/**
* 替换父节点下的子节点集合
* @param parentNode BaseNode
* @param newData Collection<BaseNode>
*/
fun nodeReplaceChildData(parentNode: BaseNode, newData: Collection<BaseNode>) {
parentNode.childNode?.let {
if (parentNode is BaseExpandNode && !parentNode.isExpanded) {
it.clear()
it.addAll(newData)
return
}
val parentIndex = this.data.indexOf(parentNode)
val removeCount = removeChildAt(parentIndex)
it.clear()
it.addAll(newData)
val newFlatData = flatData(newData)
this.data.addAll(parentIndex + 1, newFlatData)
val positionStart = parentIndex + 1 + headerLayoutCount
if (removeCount == newFlatData.size) {
notifyItemRangeChanged(positionStart, removeCount)
} else {
notifyItemRangeRemoved(positionStart, removeCount)
notifyItemRangeInserted(positionStart, newFlatData.size)
}
// notifyItemRangeChanged(parentIndex + 1 + getHeaderLayoutCount(), max(removeCount, newFlatData.size))
}
}
/*************************** Node 数据操作 END ***************************/
/**
* 将输入的嵌套类型数组循环递归,在扁平化数据的同时,设置展开状态
* @param list Collection<BaseNode>
* @param isExpanded Boolean? 如果不需要改变状态,设置为null。true 为展开,false 为收起
* @return MutableList<BaseNode>
*/
private fun flatData(list: Collection<BaseNode>, isExpanded: Boolean? = null): MutableList<BaseNode> {
val newList = ArrayList<BaseNode>()
for (element in list) {
newList.add(element)
if (element is BaseExpandNode) {
// 如果是展开状态 或者需要设置为展开状态
if (isExpanded == true || element.isExpanded) {
val childNode = element.childNode
if (!childNode.isNullOrEmpty()) {
val items = flatData(childNode, isExpanded)
newList.addAll(items)
}
}
isExpanded?.let {
element.isExpanded = it
}
} else {
val childNode = element.childNode
if (!childNode.isNullOrEmpty()) {
val items = flatData(childNode, isExpanded)
newList.addAll(items)
}
}
if (element is NodeFooterImp) {
element.footerNode?.let {
newList.add(it)
}
}
}
return newList
}
/**
* 收起Node
* 私有方法,为减少递归复杂度,不对外暴露 isChangeChildExpand 参数,防止错误设置
*
* @param position Int
* @param isChangeChildCollapse Boolean 是否改变子 node 的状态为收起,true 为跟随变为收起,false 表示保持原状态。
* @param animate Boolean
* @param notify Boolean
*/
private fun collapse(@IntRange(from = 0) position: Int,
isChangeChildCollapse: Boolean = false,
animate: Boolean = true,
notify: Boolean = true,
parentPayload: Any? = null): Int {
val node = this.data[position]
if (node is BaseExpandNode && node.isExpanded) {
val adapterPosition = position + headerLayoutCount
node.isExpanded = false
if (node.childNode.isNullOrEmpty()) {
notifyItemChanged(adapterPosition, parentPayload)
return 0
}
val items = flatData(node.childNode!!, if (isChangeChildCollapse) false else null)
val size = items.size
this.data.removeAll(items)
if (notify) {
if (animate) {
notifyItemChanged(adapterPosition, parentPayload)
notifyItemRangeRemoved(adapterPosition + 1, size)
} else {
notifyDataSetChanged()
}
}
return size
}
return 0
}
/**
* 展开Node
* 私有方法,为减少递归复杂度,不对外暴露 isChangeChildExpand 参数,防止错误设置
*
* @param position Int
* @param isChangeChildExpand Boolean 是否改变子 node 的状态为展开,true 为跟随变为展开,false 表示保持原状态。
* @param animate Boolean
* @param notify Boolean
*/
private fun expand(@IntRange(from = 0) position: Int,
isChangeChildExpand: Boolean = false,
animate: Boolean = true,
notify: Boolean = true,
parentPayload: Any? = null): Int {
val node = this.data[position]
if (node is BaseExpandNode && !node.isExpanded) {
val adapterPosition = position + headerLayoutCount
node.isExpanded = true
if (node.childNode.isNullOrEmpty()) {
notifyItemChanged(adapterPosition, parentPayload)
return 0
}
val items = flatData(node.childNode!!, if (isChangeChildExpand) true else null)
val size = items.size
this.data.addAll(position + 1, items)
if (notify) {
if (animate) {
notifyItemChanged(adapterPosition, parentPayload)
notifyItemRangeInserted(adapterPosition + 1, size)
} else {
notifyDataSetChanged()
}
}
return size
}
return 0
}
/**
* 收起 node
* @param position Int
* @param animate Boolean
* @param notify Boolean
*/
@JvmOverloads
fun collapse(@IntRange(from = 0) position: Int,
animate: Boolean = true,
notify: Boolean = true,
parentPayload: Any? = null): Int {
return collapse(position, false, animate, notify, parentPayload)
}
/**
* 展开 node
* @param position Int
* @param animate Boolean
* @param notify Boolean
*/
@JvmOverloads
fun expand(@IntRange(from = 0) position: Int,
animate: Boolean = true,
notify: Boolean = true,
parentPayload: Any? = null): Int {
return expand(position, false, animate, notify, parentPayload)
}
/**
* 收起或展开Node
* @param position Int
* @param animate Boolean
* @param notify Boolean
*/
@JvmOverloads
fun expandOrCollapse(@IntRange(from = 0) position: Int,
animate: Boolean = true,
notify: Boolean = true,
parentPayload: Any? = null): Int {
val node = this.data[position]
if (node is BaseExpandNode) {
return if (node.isExpanded) {
collapse(position, false, animate, notify, parentPayload)
} else {
expand(position, false, animate, notify, parentPayload)
}
}
return 0
}
@JvmOverloads
fun expandAndChild(@IntRange(from = 0) position: Int,
animate: Boolean = true,
notify: Boolean = true,
parentPayload: Any? = null): Int {
return expand(position, true, animate, notify, parentPayload)
}
@JvmOverloads
fun collapseAndChild(@IntRange(from = 0) position: Int,
animate: Boolean = true,
notify: Boolean = true,
parentPayload: Any? = null): Int {
return collapse(position, true, animate, notify, parentPayload)
}
/**
* 展开某一个node的时候,折叠其他node
* @param position Int
* @param isExpandedChild Boolean 展开的时候,是否展开子项目
* @param isCollapseChild Boolean 折叠其他node的时候,是否折叠子项目
* @param animate Boolean
* @param notify Boolean
*/
@JvmOverloads
fun expandAndCollapseOther(@IntRange(from = 0) position: Int,
isExpandedChild: Boolean = false,
isCollapseChild: Boolean = true,
animate: Boolean = true,
notify: Boolean = true,
expandPayload: Any? = null,
collapsePayload: Any? = null) {
val expandCount = expand(position, isExpandedChild, animate, notify, expandPayload)
if (expandCount == 0) {
return
}
val parentPosition = findParentNode(position)
// 当前层级顶部开始位置
val firstPosition: Int = if (parentPosition == -1) {
0 // 如果没有父节点,则为最外层,从0开始
} else {
parentPosition + 1 // 如果有父节点,则为子节点,从 父节点+1 的位置开始
}
// 当前 position 之前有 node 收起以后,position的位置就会变化
var newPosition = position
// 在此之前的 node 总数
val beforeAllSize = position - firstPosition
// 如果此 position 之前有 node
if (beforeAllSize > 0) {
// 从顶部开始位置循环
var i = firstPosition
do {
val collapseSize = collapse(i, isCollapseChild, animate, notify, collapsePayload)
i++
// 每次折叠后,重新计算新的 Position
newPosition -= collapseSize
} while (i < newPosition)
}
// 当前层级最后的位置
var lastPosition: Int = if (parentPosition == -1) {
data.size - 1 // 如果没有父节点,则为最外层
} else {
val dataSize = data[parentPosition].childNode?.size ?: 0
parentPosition + dataSize + expandCount // 如果有父节点,则为子节点,父节点 + 子节点数量 + 展开的数量
}
//如果此 position 之后有 node
if ((newPosition + expandCount) < lastPosition) {
var i = newPosition + expandCount + 1
while (i <= lastPosition) {
val collapseSize = collapse(i, isCollapseChild, animate, notify, collapsePayload)
i++
lastPosition -= collapseSize
}
}
}
/**
* 查找父节点。如果不存在,则返回-1
* @param node BaseNode
* @return Int 父节点的position
*/
fun findParentNode(node: BaseNode): Int {
val pos = this.data.indexOf(node)
if (pos == -1 || pos == 0) {
return -1
}
for (i in pos - 1 downTo 0) {
val tempNode = this.data[i]
if (tempNode.childNode?.contains(node) == true) {
return i
}
}
return -1
}
fun findParentNode(@IntRange(from = 0) position: Int): Int {
if (position == 0) {
return -1
}
val node = this.data[position]
for (i in position - 1 downTo 0) {
val tempNode = this.data[i]
if (tempNode.childNode?.contains(node) == true) {
return i
}
}
return -1
}
}
\ No newline at end of file
package com.chad.library.adapter.base
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.chad.library.adapter.base.provider.BaseItemProvider
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
* 当有多种条目的时候,避免在convert()中做太多的业务逻辑,把逻辑放在对应的 ItemProvider 中。
* 适用于以下情况:
* 1、实体类不方便扩展,此Adapter的数据类型可以是任意类型,只需要在[getItemType]中返回对应类型
* 2、item 类型较多,在convert()中管理起来复杂
*
* ViewHolder 由 [BaseItemProvider] 实现,并且每个[BaseItemProvider]可以拥有自己类型的ViewHolder类型。
*
* @param T data 数据类型
* @constructor
*/
abstract class BaseProviderMultiAdapter<T>(data: MutableList<T>? = null) :
BaseQuickAdapter<T, BaseViewHolder>(0, data) {
private val mItemProviders by lazy(LazyThreadSafetyMode.NONE) { SparseArray<BaseItemProvider<T>>() }
/**
* 返回 item 类型
* @param data List<T>
* @param position Int
* @return Int
*/
protected abstract fun getItemType(data: List<T>, position: Int): Int
/**
* 必须通过此方法,添加 provider
* @param provider BaseItemProvider
*/
open fun addItemProvider(provider: BaseItemProvider<T>) {
provider.setAdapter(this)
mItemProviders.put(provider.itemViewType, provider)
}
override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
val provider = getItemProvider(viewType)
checkNotNull(provider) { "ViewType: $viewType no such provider found,please use addItemProvider() first!" }
provider.context = parent.context
return provider.onCreateViewHolder(parent, viewType).apply {
provider.onViewHolderCreated(this, viewType)
}
}
override fun getDefItemViewType(position: Int): Int {
return getItemType(data, position)
}
override fun convert(holder: BaseViewHolder, item: T) {
getItemProvider(holder.itemViewType)!!.convert(holder, item)
}
override fun convert(holder: BaseViewHolder, item: T, payloads: List<Any>) {
getItemProvider(holder.itemViewType)!!.convert(holder, item, payloads)
}
override fun bindViewClickListener(viewHolder: BaseViewHolder, viewType: Int) {
super.bindViewClickListener(viewHolder, viewType)
bindClick(viewHolder)
bindChildClick(viewHolder, viewType)
}
/**
* 通过 ViewType 获取 BaseItemProvider
* 例如:如果ViewType经过特殊处理,可以重写此方法,获取正确的Provider
* (比如 ViewType 通过位运算进行的组合的)
*
* @param viewType Int
* @return BaseItemProvider
*/
protected open fun getItemProvider(viewType: Int): BaseItemProvider<T>? {
return mItemProviders.get(viewType)
}
override fun onViewAttachedToWindow(holder: BaseViewHolder) {
super.onViewAttachedToWindow(holder)
getItemProvider(holder.itemViewType)?.onViewAttachedToWindow(holder)
}
override fun onViewDetachedFromWindow(holder: BaseViewHolder) {
super.onViewDetachedFromWindow(holder)
getItemProvider(holder.itemViewType)?.onViewDetachedFromWindow(holder)
}
protected open fun bindClick(viewHolder: BaseViewHolder) {
if (getOnItemClickListener() == null) {
//如果没有设置点击监听,则回调给 itemProvider
//Callback to itemProvider if no click listener is set
viewHolder.itemView.setOnClickListener {
var position = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnClickListener
}
position -= headerLayoutCount
val itemViewType = viewHolder.itemViewType
val provider = mItemProviders.get(itemViewType)
provider.onClick(viewHolder, it, data[position], position)
}
}
if (getOnItemLongClickListener() == null) {
//如果没有设置长按监听,则回调给itemProvider
// If you do not set a long press listener, callback to the itemProvider
viewHolder.itemView.setOnLongClickListener {
var position = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnLongClickListener false
}
position -= headerLayoutCount
val itemViewType = viewHolder.itemViewType
val provider = mItemProviders.get(itemViewType)
provider.onLongClick(viewHolder, it, data[position], position)
}
}
}
protected open fun bindChildClick(viewHolder: BaseViewHolder, viewType: Int) {
if (getOnItemChildClickListener() == null) {
val provider = getItemProvider(viewType) ?: return
val ids = provider.getChildClickViewIds()
ids.forEach { id ->
viewHolder.itemView.findViewById<View>(id)?.let {
if (!it.isClickable) {
it.isClickable = true
}
it.setOnClickListener { v ->
var position: Int = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnClickListener
}
position -= headerLayoutCount
provider.onChildClick(viewHolder, v, data[position], position)
}
}
}
}
if (getOnItemChildLongClickListener() == null) {
val provider = getItemProvider(viewType) ?: return
val ids = provider.getChildLongClickViewIds()
ids.forEach { id ->
viewHolder.itemView.findViewById<View>(id)?.let {
if (!it.isLongClickable) {
it.isLongClickable = true
}
it.setOnLongClickListener { v ->
var position: Int = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnLongClickListener false
}
position -= headerLayoutCount
provider.onChildLongClick(viewHolder, v, data[position], position)
}
}
}
}
}
}
\ No newline at end of file
package com.chad.library.adapter.base
import android.animation.Animator
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.ViewParent
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.annotation.IdRes
import androidx.annotation.IntRange
import androidx.annotation.LayoutRes
import androidx.annotation.NonNull
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.chad.library.adapter.base.animation.*
import com.chad.library.adapter.base.diff.BrvahAsyncDiffer
import com.chad.library.adapter.base.diff.BrvahAsyncDifferConfig
import com.chad.library.adapter.base.diff.BrvahListUpdateCallback
import com.chad.library.adapter.base.listener.*
import com.chad.library.adapter.base.module.*
import com.chad.library.adapter.base.util.getItemView
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import java.lang.reflect.Constructor
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType
import java.util.*
import kotlin.collections.ArrayList
/**
* Base Class
* @param T : type of data, 数据类型
* @param VH : BaseViewHolder
* @constructor layoutId, data(Can null parameters, the default is empty data)
*/
abstract class BaseQuickAdapter<T, VH : BaseViewHolder>
@JvmOverloads constructor(@LayoutRes private val layoutResId: Int,
data: MutableList<T>? = null)
: RecyclerView.Adapter<VH>() {
companion object {
const val HEADER_VIEW = 0x10000111
const val LOAD_MORE_VIEW = 0x10000222
const val FOOTER_VIEW = 0x10000333
const val EMPTY_VIEW = 0x10000555
}
/***************************** Public property settings *************************************/
/**
* data, Only allowed to get.
* 数据, 只允许 get。
*/
var data: MutableList<T> = data ?: arrayListOf()
internal set
/**
* 当显示空布局时,是否显示 Header
*/
var headerWithEmptyEnable = false
/** 当显示空布局时,是否显示 Foot */
var footerWithEmptyEnable = false
/** 是否使用空布局 */
var isUseEmpty = true
/**
* if asFlow is true, footer/header will arrange like normal item view.
* only works when use [GridLayoutManager],and it will ignore span size.
*/
var headerViewAsFlow: Boolean = false
var footerViewAsFlow: Boolean = false
/**
* 是否打开动画
*/
var animationEnable: Boolean = false
/**
* 动画是否仅第一次执行
*/
var isAnimationFirstOnly = true
/**
* 设置自定义动画
*/
var adapterAnimation: BaseAnimation? = null
set(value) {
animationEnable = true
field = value
}
/**
* 加载更多模块
*/
val loadMoreModule: BaseLoadMoreModule
get() {
checkNotNull(mLoadMoreModule) { "Please first implements LoadMoreModule" }
return mLoadMoreModule!!
}
/**
* 向上加载模块
*/
val upFetchModule: BaseUpFetchModule
get() {
checkNotNull(mUpFetchModule) { "Please first implements UpFetchModule" }
return mUpFetchModule!!
}
/**
* 拖拽模块
*/
val draggableModule: BaseDraggableModule
get() {
checkNotNull(mDraggableModule) { "Please first implements DraggableModule" }
return mDraggableModule!!
}
/********************************* Private property *****************************************/
private var mDiffHelper: BrvahAsyncDiffer<T>? = null
private lateinit var mHeaderLayout: LinearLayout
private lateinit var mFooterLayout: LinearLayout
private lateinit var mEmptyLayout: FrameLayout
private var mLastPosition = -1
private var mSpanSizeLookup: GridSpanSizeLookup? = null
private var mOnItemClickListener: OnItemClickListener? = null
private var mOnItemLongClickListener: OnItemLongClickListener? = null
private var mOnItemChildClickListener: OnItemChildClickListener? = null
private var mOnItemChildLongClickListener: OnItemChildLongClickListener? = null
private var mUpFetchModule: BaseUpFetchModule? = null
private var mDraggableModule: BaseDraggableModule? = null
internal var mLoadMoreModule: BaseLoadMoreModule? = null
var recyclerViewOrNull: RecyclerView? = null
private set
val recyclerView: RecyclerView
get() {
checkNotNull(recyclerViewOrNull) {
"Please get it after onAttachedToRecyclerView()"
}
return recyclerViewOrNull!!
}
val context: Context
get() {
return recyclerView.context
}
/******************************* RecyclerView Method ****************************************/
init {
checkModule()
}
/**
* 检查模块
*/
private fun checkModule() {
if (this is LoadMoreModule) {
mLoadMoreModule = this.addLoadMoreModule(this)
}
if (this is UpFetchModule) {
mUpFetchModule = this.addUpFetchModule(this)
}
if (this is DraggableModule) {
mDraggableModule = this.addDraggableModule(this)
}
}
/**
* Implement this method and use the helper to adapt the view to the given item.
*
* 实现此方法,并使用 helper 完成 item 视图的操作
*
* @param holder A fully initialized helper.
* @param item The item that needs to be displayed.
*/
protected abstract fun convert(holder: VH, item: T)
/**
* Optional implementation this method and use the helper to adapt the view to the given item.
* If use [payloads], will perform this method, Please implement this method for partial refresh.
* If use [RecyclerView.Adapter.notifyItemChanged(Int, Object)] with payload,
* Will execute this method.
*
* 可选实现,如果你是用了[payloads]刷新item,请实现此方法,进行局部刷新
*
* @param holder A fully initialized helper.
* @param item The item that needs to be displayed.
* @param payloads payload info.
*/
protected open fun convert(holder: VH, item: T, payloads: List<Any>) {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val baseViewHolder: VH
when (viewType) {
LOAD_MORE_VIEW -> {
val view = mLoadMoreModule!!.loadMoreView.getRootView(parent)
baseViewHolder = createBaseViewHolder(view)
mLoadMoreModule!!.setupViewHolder(baseViewHolder)
}
HEADER_VIEW -> {
val headerLayoutVp: ViewParent? = mHeaderLayout.parent
if (headerLayoutVp is ViewGroup) {
headerLayoutVp.removeView(mHeaderLayout)
}
baseViewHolder = createBaseViewHolder(mHeaderLayout)
}
EMPTY_VIEW -> {
val emptyLayoutVp: ViewParent? = mEmptyLayout.parent
if (emptyLayoutVp is ViewGroup) {
emptyLayoutVp.removeView(mEmptyLayout)
}
baseViewHolder = createBaseViewHolder(mEmptyLayout)
}
FOOTER_VIEW -> {
val footerLayoutVp: ViewParent? = mFooterLayout.parent
if (footerLayoutVp is ViewGroup) {
footerLayoutVp.removeView(mFooterLayout)
}
baseViewHolder = createBaseViewHolder(mFooterLayout)
}
else -> {
val viewHolder = onCreateDefViewHolder(parent, viewType)
bindViewClickListener(viewHolder, viewType)
mDraggableModule?.initView(viewHolder)
onItemViewHolderCreated(viewHolder, viewType)
baseViewHolder = viewHolder
}
}
return baseViewHolder
}
/**
* Don't override this method. If need, please override [getDefItemCount]
* 不要重写此方法,如果有需要,请重写[getDefItemCount]
* @return Int
*/
override fun getItemCount(): Int {
if (hasEmptyView()) {
var count = 1
if (headerWithEmptyEnable && hasHeaderLayout()) {
count++
}
if (footerWithEmptyEnable && hasFooterLayout()) {
count++
}
return count
} else {
val loadMoreCount = if (mLoadMoreModule?.hasLoadMoreView() == true) {
1
} else {
0
}
return headerLayoutCount + getDefItemCount() + footerLayoutCount + loadMoreCount
}
}
/**
* Don't override this method. If need, please override [getDefItemViewType]
* 不要重写此方法,如果有需要,请重写[getDefItemViewType]
*
* @param position Int
* @return Int
*/
override fun getItemViewType(position: Int): Int {
if (hasEmptyView()) {
val header = headerWithEmptyEnable && hasHeaderLayout()
return when (position) {
0 -> if (header) {
HEADER_VIEW
} else {
EMPTY_VIEW
}
1 -> if (header) {
EMPTY_VIEW
} else {
FOOTER_VIEW
}
2 -> FOOTER_VIEW
else -> EMPTY_VIEW
}
}
val hasHeader = hasHeaderLayout()
if (hasHeader && position == 0) {
return HEADER_VIEW
} else {
var adjPosition = if (hasHeader) {
position - 1
} else {
position
}
val dataSize = data.size
return if (adjPosition < dataSize) {
getDefItemViewType(adjPosition)
} else {
adjPosition -= dataSize
val numFooters = if (hasFooterLayout()) {
1
} else {
0
}
if (adjPosition < numFooters) {
FOOTER_VIEW
} else {
LOAD_MORE_VIEW
}
}
}
}
override fun onBindViewHolder(holder: VH, position: Int) {
//Add up fetch logic, almost like load more, but simpler.
mUpFetchModule?.autoUpFetch(position)
//Do not move position, need to change before LoadMoreView binding
mLoadMoreModule?.autoLoadMore(position)
when (holder.itemViewType) {
LOAD_MORE_VIEW -> {
mLoadMoreModule?.let {
it.loadMoreView.convert(holder, position, it.loadMoreStatus)
}
}
HEADER_VIEW, EMPTY_VIEW, FOOTER_VIEW -> return
else -> convert(holder, getItem(position - headerLayoutCount))
}
}
override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position)
return
}
//Add up fetch logic, almost like load more, but simpler.
mUpFetchModule?.autoUpFetch(position)
//Do not move position, need to change before LoadMoreView binding
mLoadMoreModule?.autoLoadMore(position)
when (holder.itemViewType) {
LOAD_MORE_VIEW -> {
mLoadMoreModule?.let {
it.loadMoreView.convert(holder, position, it.loadMoreStatus)
}
}
HEADER_VIEW, EMPTY_VIEW, FOOTER_VIEW -> return
else -> convert(holder, getItem(position - headerLayoutCount), payloads)
}
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
/**
* Called when a view created by this holder has been attached to a window.
* simple to solve item will layout using all
* [setFullSpan]
*
* @param holder
*/
override fun onViewAttachedToWindow(holder: VH) {
super.onViewAttachedToWindow(holder)
val type = holder.itemViewType
if (isFixedViewType(type)) {
setFullSpan(holder)
} else {
addAnimation(holder)
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
recyclerViewOrNull = recyclerView
mDraggableModule?.attachToRecyclerView(recyclerView)
val manager = recyclerView.layoutManager
if (manager is GridLayoutManager) {
val defSpanSizeLookup = manager.spanSizeLookup
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
val type = getItemViewType(position)
if (type == HEADER_VIEW && headerViewAsFlow) {
return 1
}
if (type == FOOTER_VIEW && footerViewAsFlow) {
return 1
}
return if (mSpanSizeLookup == null) {
if (isFixedViewType(type)) manager.spanCount else defSpanSizeLookup.getSpanSize(position)
} else {
if (isFixedViewType(type))
manager.spanCount
else
mSpanSizeLookup!!.getSpanSize(manager, type, position - headerLayoutCount)
}
}
}
}
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
recyclerViewOrNull = null
}
protected open fun isFixedViewType(type: Int): Boolean {
return type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOAD_MORE_VIEW
}
/**
* Get the data item associated with the specified position in the data set.
*
* @param position Position of the item whose data we want within the adapter's
* data set.
* @return The data at the specified position.
*/
open fun getItem(@IntRange(from = 0) position: Int): T {
return data[position]
}
open fun getItemOrNull(@IntRange(from = 0) position: Int): T? {
return data.getOrNull(position)
}
/**
* 如果返回 -1,表示不存在
* @param item T?
* @return Int
*/
open fun getItemPosition(item: T?): Int {
return if (item != null && data.isNotEmpty()) data.indexOf(item) else -1
}
/**
* 用于保存需要设置点击事件的 item
*/
private val childClickViewIds = LinkedHashSet<Int>()
fun getChildClickViewIds(): LinkedHashSet<Int> {
return childClickViewIds
}
/**
* 设置需要点击事件的子view
* @param viewIds IntArray
*/
fun addChildClickViewIds(@IdRes vararg viewIds: Int) {
for (viewId in viewIds) {
childClickViewIds.add(viewId)
}
}
/**
* 用于保存需要设置长按点击事件的 item
*/
private val childLongClickViewIds = LinkedHashSet<Int>()
fun getChildLongClickViewIds(): LinkedHashSet<Int> {
return childLongClickViewIds
}
/**
* 设置需要长按点击事件的子view
* @param viewIds IntArray
*/
fun addChildLongClickViewIds(@IdRes vararg viewIds: Int) {
for (viewId in viewIds) {
childLongClickViewIds.add(viewId)
}
}
/**
* 绑定 item 点击事件
* @param viewHolder VH
*/
protected open fun bindViewClickListener(viewHolder: VH, viewType: Int) {
mOnItemClickListener?.let {
viewHolder.itemView.setOnClickListener { v ->
var position = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnClickListener
}
position -= headerLayoutCount
setOnItemClick(v, position)
}
}
mOnItemLongClickListener?.let {
viewHolder.itemView.setOnLongClickListener { v ->
var position = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnLongClickListener false
}
position -= headerLayoutCount
setOnItemLongClick(v, position)
}
}
mOnItemChildClickListener?.let {
for (id in getChildClickViewIds()) {
viewHolder.itemView.findViewById<View>(id)?.let { childView ->
if (!childView.isClickable) {
childView.isClickable = true
}
childView.setOnClickListener { v ->
var position = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnClickListener
}
position -= headerLayoutCount
setOnItemChildClick(v, position)
}
}
}
}
mOnItemChildLongClickListener?.let {
for (id in getChildLongClickViewIds()) {
viewHolder.itemView.findViewById<View>(id)?.let { childView ->
if (!childView.isLongClickable) {
childView.isLongClickable = true
}
childView.setOnLongClickListener { v ->
var position = viewHolder.bindingAdapterPosition
if (position == RecyclerView.NO_POSITION) {
return@setOnLongClickListener false
}
position -= headerLayoutCount
setOnItemChildLongClick(v, position)
}
}
}
}
}
/**
* override this method if you want to override click event logic
*
* 如果你想重新实现 item 点击事件逻辑,请重写此方法
* @param v
* @param position
*/
protected open fun setOnItemClick(v: View, position: Int) {
mOnItemClickListener?.onItemClick(this, v, position)
}
/**
* override this method if you want to override longClick event logic
*
* 如果你想重新实现 item 长按事件逻辑,请重写此方法
* @param v
* @param position
* @return
*/
protected open fun setOnItemLongClick(v: View, position: Int): Boolean {
return mOnItemLongClickListener?.onItemLongClick(this, v, position) ?: false
}
protected open fun setOnItemChildClick(v: View, position: Int) {
mOnItemChildClickListener?.onItemChildClick(this, v, position)
}
protected open fun setOnItemChildLongClick(v: View, position: Int): Boolean {
return mOnItemChildLongClickListener?.onItemChildLongClick(this, v, position) ?: false
}
/**
* (可选重写)当 item 的 ViewHolder创建完毕后,执行此方法。
* 可在此对 ViewHolder 进行处理,例如进行 DataBinding 绑定 view
*
* @param viewHolder VH
* @param viewType Int
*/
protected open fun onItemViewHolderCreated(viewHolder: VH, viewType: Int) {}
/**
* Override this method and return your data size.
* 重写此方法,返回你的数据数量。
*/
protected open fun getDefItemCount(): Int {
return data.size
}
/**
* Override this method and return your ViewType.
* 重写此方法,返回你的ViewType。
*/
protected open fun getDefItemViewType(position: Int): Int {
return super.getItemViewType(position)
}
/**
* Override this method and return your ViewHolder.
* 重写此方法,返回你的ViewHolder。
*/
protected open fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VH {
return createBaseViewHolder(parent, layoutResId)
}
protected open fun createBaseViewHolder(parent: ViewGroup, @LayoutRes layoutResId: Int): VH {
return createBaseViewHolder(parent.getItemView(layoutResId))
}
/**
* 创建 ViewHolder。可以重写
*
* @param view View
* @return VH
*/
@Suppress("UNCHECKED_CAST")
protected open fun createBaseViewHolder(view: View): VH {
var temp: Class<*>? = javaClass
var z: Class<*>? = null
while (z == null && null != temp) {
z = getInstancedGenericKClass(temp)
temp = temp.superclass
}
// 泛型擦除会导致z为null
val vh: VH? = if (z == null) {
BaseViewHolder(view) as VH
} else {
createBaseGenericKInstance(z, view)
}
return vh ?: BaseViewHolder(view) as VH
}
/**
* get generic parameter VH
*
* @param z
* @return
*/
private fun getInstancedGenericKClass(z: Class<*>): Class<*>? {
try {
val type = z.genericSuperclass
if (type is ParameterizedType) {
val types = type.actualTypeArguments
for (temp in types) {
if (temp is Class<*>) {
if (BaseViewHolder::class.java.isAssignableFrom(temp)) {
return temp
}
} else if (temp is ParameterizedType) {
val rawType = temp.rawType
if (rawType is Class<*> && BaseViewHolder::class.java.isAssignableFrom(rawType)) {
return rawType
}
}
}
}
} catch (e: java.lang.reflect.GenericSignatureFormatError) {
e.printStackTrace()
} catch (e: TypeNotPresentException) {
e.printStackTrace()
} catch (e: java.lang.reflect.MalformedParameterizedTypeException) {
e.printStackTrace()
}
return null
}
/**
* try to create Generic VH instance
*
* @param z
* @param view
* @return
*/
@Suppress("UNCHECKED_CAST")
private fun createBaseGenericKInstance(z: Class<*>, view: View): VH? {
try {
val constructor: Constructor<*>
// inner and unstatic class
return if (z.isMemberClass && !Modifier.isStatic(z.modifiers)) {
constructor = z.getDeclaredConstructor(javaClass, View::class.java)
constructor.isAccessible = true
constructor.newInstance(this, view) as VH
} else {
constructor = z.getDeclaredConstructor(View::class.java)
constructor.isAccessible = true
constructor.newInstance(view) as VH
}
} catch (e: NoSuchMethodException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InstantiationException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
}
return null
}
/**
* When set to true, the item will layout using all span area. That means, if orientation
* is vertical, the view will have full width; if orientation is horizontal, the view will
* have full height.
* if the hold view use StaggeredGridLayoutManager they should using all span area
*
* @param holder True if this item should traverse all spans.
*/
protected open fun setFullSpan(holder: RecyclerView.ViewHolder) {
val layoutParams = holder.itemView.layoutParams
if (layoutParams is StaggeredGridLayoutManager.LayoutParams) {
layoutParams.isFullSpan = true
}
}
/**
* get the specific view by position,e.g. getViewByPosition(2, R.id.textView)
*
* bind [RecyclerView.setAdapter] before use!
*/
fun getViewByPosition(position: Int, @IdRes viewId: Int): View? {
val recyclerView = recyclerViewOrNull ?: return null
val viewHolder = recyclerView.findViewHolderForLayoutPosition(position) as BaseViewHolder?
?: return null
return viewHolder.getViewOrNull(viewId)
}
/********************************************************************************************/
/********************************* HeaderView Method ****************************************/
/********************************************************************************************/
@JvmOverloads
fun addHeaderView(view: View, index: Int = -1, orientation: Int = LinearLayout.VERTICAL): Int {
if (!this::mHeaderLayout.isInitialized) {
mHeaderLayout = LinearLayout(view.context)
mHeaderLayout.orientation = orientation
mHeaderLayout.layoutParams = if (orientation == LinearLayout.VERTICAL) {
RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
} else {
RecyclerView.LayoutParams(WRAP_CONTENT, MATCH_PARENT)
}
}
val childCount = mHeaderLayout.childCount
var mIndex = index
if (index < 0 || index > childCount) {
mIndex = childCount
}
mHeaderLayout.addView(view, mIndex)
if (mHeaderLayout.childCount == 1) {
val position = headerViewPosition
if (position != -1) {
notifyItemInserted(position)
}
}
return mIndex
}
@JvmOverloads
fun setHeaderView(view: View, index: Int = 0, orientation: Int = LinearLayout.VERTICAL): Int {
return if (!this::mHeaderLayout.isInitialized || mHeaderLayout.childCount <= index) {
addHeaderView(view, index, orientation)
} else {
mHeaderLayout.removeViewAt(index)
mHeaderLayout.addView(view, index)
index
}
}
/**
* 是否有 HeaderLayout
* @return Boolean
*/
fun hasHeaderLayout(): Boolean {
if (this::mHeaderLayout.isInitialized && mHeaderLayout.childCount > 0) {
return true
}
return false
}
fun removeHeaderView(header: View) {
if (!hasHeaderLayout()) return
mHeaderLayout.removeView(header)
if (mHeaderLayout.childCount == 0) {
val position = headerViewPosition
if (position != -1) {
notifyItemRemoved(position)
}
}
}
fun removeAllHeaderView() {
if (!hasHeaderLayout()) return
mHeaderLayout.removeAllViews()
val position = headerViewPosition
if (position != -1) {
notifyItemRemoved(position)
}
}
val headerViewPosition: Int
get() {
if (hasEmptyView()) {
if (headerWithEmptyEnable) {
return 0
}
} else {
return 0
}
return -1
}
/**
* if addHeaderView will be return 1, if not will be return 0
*/
val headerLayoutCount: Int
get() {
return if (hasHeaderLayout()) {
1
} else {
0
}
}
/**
* 获取头布局
*/
val headerLayout: LinearLayout?
get() {
return if (this::mHeaderLayout.isInitialized) {
mHeaderLayout
} else {
null
}
}
/********************************************************************************************/
/********************************* FooterView Method ****************************************/
/********************************************************************************************/
@JvmOverloads
fun addFooterView(view: View, index: Int = -1, orientation: Int = LinearLayout.VERTICAL): Int {
if (!this::mFooterLayout.isInitialized) {
mFooterLayout = LinearLayout(view.context)
mFooterLayout.orientation = orientation
mFooterLayout.layoutParams = if (orientation == LinearLayout.VERTICAL) {
RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
} else {
RecyclerView.LayoutParams(WRAP_CONTENT, MATCH_PARENT)
}
}
val childCount = mFooterLayout.childCount
var mIndex = index
if (index < 0 || index > childCount) {
mIndex = childCount
}
mFooterLayout.addView(view, mIndex)
if (mFooterLayout.childCount == 1) {
val position = footerViewPosition
if (position != -1) {
notifyItemInserted(position)
}
}
return mIndex
}
@JvmOverloads
fun setFooterView(view: View, index: Int = 0, orientation: Int = LinearLayout.VERTICAL): Int {
return if (!this::mFooterLayout.isInitialized || mFooterLayout.childCount <= index) {
addFooterView(view, index, orientation)
} else {
mFooterLayout.removeViewAt(index)
mFooterLayout.addView(view, index)
index
}
}
fun removeFooterView(footer: View) {
if (!hasFooterLayout()) return
mFooterLayout.removeView(footer)
if (mFooterLayout.childCount == 0) {
val position = footerViewPosition
if (position != -1) {
notifyItemRemoved(position)
}
}
}
fun removeAllFooterView() {
if (!hasFooterLayout()) return
mFooterLayout.removeAllViews()
val position = footerViewPosition
if (position != -1) {
notifyItemRemoved(position)
}
}
fun hasFooterLayout(): Boolean {
if (this::mFooterLayout.isInitialized && mFooterLayout.childCount > 0) {
return true
}
return false
}
val footerViewPosition: Int
get() {
if (hasEmptyView()) {
var position = 1
if (headerWithEmptyEnable && hasHeaderLayout()) {
position++
}
if (footerWithEmptyEnable) {
return position
}
} else {
return headerLayoutCount + data.size
}
return -1
}
/**
* if addHeaderView will be return 1, if not will be return 0
*/
val footerLayoutCount: Int
get() {
return if (hasFooterLayout()) {
1
} else {
0
}
}
/**
* 获取脚布局
* @return LinearLayout?
*/
val footerLayout: LinearLayout?
get() {
return if (this::mFooterLayout.isInitialized) {
mFooterLayout
} else {
null
}
}
/********************************************************************************************/
/********************************** EmptyView Method ****************************************/
/********************************************************************************************/
/**
* 设置空布局视图,注意:[data]必须为空数组
* @param emptyView View
*/
fun setEmptyView(emptyView: View) {
val oldItemCount = itemCount
var insert = false
if (!this::mEmptyLayout.isInitialized) {
mEmptyLayout = FrameLayout(emptyView.context)
mEmptyLayout.layoutParams = emptyView.layoutParams?.let {
return@let ViewGroup.LayoutParams(it.width, it.height)
} ?: ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
insert = true
} else {
emptyView.layoutParams?.let {
val lp = mEmptyLayout.layoutParams
lp.width = it.width
lp.height = it.height
mEmptyLayout.layoutParams = lp
}
}
mEmptyLayout.removeAllViews()
mEmptyLayout.addView(emptyView)
isUseEmpty = true
if (insert && hasEmptyView()) {
var position = 0
if (headerWithEmptyEnable && hasHeaderLayout()) {
position++
}
if (itemCount > oldItemCount) {
notifyItemInserted(position)
} else {
notifyDataSetChanged()
}
}
}
fun setEmptyView(layoutResId: Int) {
recyclerViewOrNull?.let {
val view = LayoutInflater.from(it.context).inflate(layoutResId, it, false)
setEmptyView(view)
}
}
fun removeEmptyView() {
if (this::mEmptyLayout.isInitialized) {
mEmptyLayout.removeAllViews()
}
}
fun hasEmptyView(): Boolean {
if (!this::mEmptyLayout.isInitialized || mEmptyLayout.childCount == 0) {
return false
}
if (!isUseEmpty) {
return false
}
return data.isEmpty()
}
/**
* When the current adapter is empty, the BaseQuickAdapter can display a special view
* called the empty view. The empty view is used to provide feedback to the user
* that no data is available in this AdapterView.
*
* @return The view to show if the adapter is empty.
*/
val emptyLayout: FrameLayout?
get() {
return if (this::mEmptyLayout.isInitialized) {
mEmptyLayout
} else {
null
}
}
/*************************** Animation ******************************************/
/**
* add animation when you want to show time
*
* @param holder
*/
private fun addAnimation(holder: RecyclerView.ViewHolder) {
if (animationEnable) {
if (!isAnimationFirstOnly || holder.layoutPosition > mLastPosition) {
val animation: BaseAnimation = adapterAnimation ?: AlphaInAnimation()
animation.animators(holder.itemView).forEach {
startAnim(it, holder.layoutPosition)
}
mLastPosition = holder.layoutPosition
}
}
}
/**
* 开始执行动画方法
* 可以重写此方法,实行更多行为
*
* @param anim
* @param index
*/
protected open fun startAnim(anim: Animator, index: Int) {
anim.start()
}
/**
* 内置默认动画类型
*/
enum class AnimationType {
AlphaIn, ScaleIn, SlideInBottom, SlideInLeft, SlideInRight
}
/**
* 使用内置默认动画设置
* @param animationType AnimationType
*/
fun setAnimationWithDefault(animationType: AnimationType) {
adapterAnimation = when (animationType) {
AnimationType.AlphaIn -> AlphaInAnimation()
AnimationType.ScaleIn -> ScaleInAnimation()
AnimationType.SlideInBottom -> SlideInBottomAnimation()
AnimationType.SlideInLeft -> SlideInLeftAnimation()
AnimationType.SlideInRight -> SlideInRightAnimation()
}
}
/*************************** 设置数据相关 ******************************************/
/**
* setting up a new instance to data;
* 设置新的数据实例
*
* @param data
*/
@Deprecated("Please use setNewInstance(), This method will be removed in the next version", replaceWith = ReplaceWith("setNewInstance(data)"))
open fun setNewData(data: MutableList<T>?) {
setNewInstance(data)
}
/**
* setting up a new instance to data;
* 设置新的数据实例,替换原有内存引用。
* 通常情况下,如非必要,请使用[setList]修改内容
*
* @param list
*/
open fun setNewInstance(list: MutableList<T>?) {
if (list === this.data) {
return
}
this.data = list ?: arrayListOf()
mLoadMoreModule?.reset()
mLastPosition = -1
notifyDataSetChanged()
mLoadMoreModule?.checkDisableLoadMoreIfNotFullPage()
}
/**
* use data to replace all item in mData. this method is different [setList],
* it doesn't change the [BaseQuickAdapter.data] reference
* Deprecated, Please use [setList]
*
* @param newData data collection
*/
@Deprecated("Please use setData()", replaceWith = ReplaceWith("setList(newData)"))
open fun replaceData(newData: Collection<T>) {
setList(newData)
}
/**
* 使用新的数据集合,改变原有数据集合内容。
* 注意:不会替换原有的内存引用,只是替换内容
*
* @param list Collection<T>?
*/
open fun setList(list: Collection<T>?) {
if (list !== this.data) {
this.data.clear()
if (!list.isNullOrEmpty()) {
this.data.addAll(list)
}
} else {
if (!list.isNullOrEmpty()) {
val newList = ArrayList(list)
this.data.clear()
this.data.addAll(newList)
} else {
this.data.clear()
}
}
mLoadMoreModule?.reset()
mLastPosition = -1
notifyDataSetChanged()
mLoadMoreModule?.checkDisableLoadMoreIfNotFullPage()
}
/**
* change data
* 改变某一位置数据
*/
open fun setData(@IntRange(from = 0) index: Int, data: T) {
if (index >= this.data.size) {
return
}
this.data[index] = data
notifyItemChanged(index + headerLayoutCount)
}
/**
* add one new data in to certain location
* 在指定位置添加一条新数据
*
* @param position
*/
open fun addData(@IntRange(from = 0) position: Int, data: T) {
this.data.add(position, data)
notifyItemInserted(position + headerLayoutCount)
compatibilityDataSizeChanged(1)
}
/**
* add one new data
* 添加一条新数据
*/
open fun addData(@NonNull data: T) {
this.data.add(data)
notifyItemInserted(this.data.size + headerLayoutCount)
compatibilityDataSizeChanged(1)
}
/**
* add new data in to certain location
* 在指定位置添加数据
*
* @param position the insert position
* @param newData the new data collection
*/
open fun addData(@IntRange(from = 0) position: Int, newData: Collection<T>) {
this.data.addAll(position, newData)
notifyItemRangeInserted(position + headerLayoutCount, newData.size)
compatibilityDataSizeChanged(newData.size)
}
open fun addData(@NonNull newData: Collection<T>) {
this.data.addAll(newData)
notifyItemRangeInserted(this.data.size - newData.size + headerLayoutCount, newData.size)
compatibilityDataSizeChanged(newData.size)
}
/**
* remove the item associated with the specified position of adapter
* 删除指定位置的数据
*
* @param position
*/
@Deprecated("Please use removeAt()", replaceWith = ReplaceWith("removeAt(position)"))
open fun remove(@IntRange(from = 0) position: Int) {
removeAt(position)
}
/**
* remove the item associated with the specified position of adapter
* 删除指定位置的数据
*
* @param position
*/
open fun removeAt(@IntRange(from = 0) position: Int) {
if (position >= data.size) {
return
}
this.data.removeAt(position)
val internalPosition = position + headerLayoutCount
notifyItemRemoved(internalPosition)
compatibilityDataSizeChanged(0)
notifyItemRangeChanged(internalPosition, this.data.size - internalPosition)
}
open fun remove(data: T) {
val index = this.data.indexOf(data)
if (index == -1) {
return
}
removeAt(index)
}
/**
* compatible getLoadMoreViewCount and getEmptyViewCount may change
*
* @param size Need compatible data size
*/
protected fun compatibilityDataSizeChanged(size: Int) {
if (this.data.size == size) {
notifyDataSetChanged()
}
}
/**
* 设置Diff Callback,用于快速生成 Diff Config。
*
* @param diffCallback ItemCallback<T>
*/
fun setDiffCallback(diffCallback: DiffUtil.ItemCallback<T>) {
this.setDiffConfig(BrvahAsyncDifferConfig.Builder(diffCallback).build())
}
/**
* 设置Diff Config。如需自定义线程,请使用此方法。
* 在使用 [setDiffNewData] 前,必须设置此方法
* @param config BrvahAsyncDifferConfig<T>
*/
fun setDiffConfig(config: BrvahAsyncDifferConfig<T>) {
mDiffHelper = BrvahAsyncDiffer(this, config)
}
@Deprecated("User getDiffer()", replaceWith = ReplaceWith("getDiffer()"))
fun getDiffHelper(): BrvahAsyncDiffer<T> {
return getDiffer()
}
fun getDiffer(): BrvahAsyncDiffer<T> {
checkNotNull(mDiffHelper) {
"Please use setDiffCallback() or setDiffConfig() first!"
}
return mDiffHelper!!
}
/**
* 使用 Diff 设置新实例.
* 此方法为异步Diff,无需考虑性能问题.
* 使用之前请先设置 [setDiffCallback] 或者 [setDiffConfig].
*
* Use Diff setting up a new instance to data.
* This method is asynchronous.
*
* @param list MutableList<T>?
*/
@JvmOverloads
open fun setDiffNewData(list: MutableList<T>?, commitCallback: Runnable? = null) {
if (hasEmptyView()) {
// If the current view is an empty view, set the new data directly without diff
setNewInstance(list)
commitCallback?.run()
return
}
mDiffHelper?.submitList(list, commitCallback)
}
/**
* 使用 DiffResult 设置新实例.
* Use DiffResult setting up a new instance to data.
*
* @param diffResult DiffResult
* @param list New Data
*/
open fun setDiffNewData(@NonNull diffResult: DiffUtil.DiffResult, list: MutableList<T>) {
if (hasEmptyView()) {
// If the current view is an empty view, set the new data directly without diff
setNewInstance(list)
return
}
diffResult.dispatchUpdatesTo(BrvahListUpdateCallback(this))
this.data = list
}
/************************************** Set Listener ****************************************/
fun setGridSpanSizeLookup(spanSizeLookup: GridSpanSizeLookup?) {
this.mSpanSizeLookup = spanSizeLookup
}
fun setOnItemClickListener(listener: OnItemClickListener?) {
this.mOnItemClickListener = listener
}
fun setOnItemLongClickListener(listener: OnItemLongClickListener?) {
this.mOnItemLongClickListener = listener
}
fun setOnItemChildClickListener(listener: OnItemChildClickListener?) {
this.mOnItemChildClickListener = listener
}
fun setOnItemChildLongClickListener(listener: OnItemChildLongClickListener?) {
this.mOnItemChildLongClickListener = listener
}
fun getOnItemClickListener(): OnItemClickListener? = mOnItemClickListener
fun getOnItemLongClickListener(): OnItemLongClickListener? = mOnItemLongClickListener
fun getOnItemChildClickListener(): OnItemChildClickListener? = mOnItemChildClickListener
fun getOnItemChildLongClickListener(): OnItemChildLongClickListener? = mOnItemChildLongClickListener
}
package com.chad.library.adapter.base
import androidx.annotation.LayoutRes
import com.chad.library.adapter.base.entity.SectionEntity
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
* 快速实现带头部的 Adapter,由于本质属于多布局,所以继承自[BaseMultiItemQuickAdapter]
* @param T : SectionEntity
* @param VH : BaseViewHolder
* @property sectionHeadResId Int
* @constructor
*/
abstract class BaseSectionQuickAdapter<T : SectionEntity, VH : BaseViewHolder>
@JvmOverloads constructor(@LayoutRes private val sectionHeadResId: Int,
data: MutableList<T>? = null)
: BaseMultiItemQuickAdapter<T, VH>(data) {
constructor(@LayoutRes sectionHeadResId: Int,
@LayoutRes layoutResId: Int,
data: MutableList<T>? = null) : this(sectionHeadResId, data) {
setNormalLayout(layoutResId)
}
init {
addItemType(SectionEntity.HEADER_TYPE, sectionHeadResId)
}
/**
* 重写此处,设置 Header
* @param helper ViewHolder
* @param item data
*/
protected abstract fun convertHeader(helper: VH, item: T)
/**
* 重写此处,设置 Diff Header
* @param helper VH
* @param item T?
* @param payloads MutableList<Any>
*/
protected open fun convertHeader(helper: VH, item: T, payloads: MutableList<Any>) {}
/**
* 如果 item 不是多布局,可以使用此方法快速设置 item layout
* 如果需要多布局 item,请使用[addItemType]
* @param layoutResId Int
*/
protected fun setNormalLayout(@LayoutRes layoutResId: Int) {
addItemType(SectionEntity.NORMAL_TYPE, layoutResId)
}
override fun isFixedViewType(type: Int): Boolean {
return super.isFixedViewType(type) || type == SectionEntity.HEADER_TYPE
}
override fun onBindViewHolder(holder: VH, position: Int) {
if (holder.itemViewType == SectionEntity.HEADER_TYPE) {
// setFullSpan(holder)
convertHeader(holder, getItem(position - headerLayoutCount))
} else {
super.onBindViewHolder(holder, position)
}
}
override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position)
return
}
if (holder.itemViewType == SectionEntity.HEADER_TYPE) {
convertHeader(holder, getItem(position - headerLayoutCount), payloads)
} else {
super.onBindViewHolder(holder, position, payloads)
}
}
}
\ No newline at end of file
package com.chad.library.adapter.base.animation
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
import android.view.animation.LinearInterpolator
/**
* https://github.com/CymChad/BaseRecyclerViewAdapterHelper
*/
class AlphaInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_ALPHA_FROM) : BaseAnimation {
override fun animators(view: View): Array<Animator> {
val animator = ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f)
animator.duration = 300L
animator.interpolator = LinearInterpolator()
return arrayOf(animator)
}
companion object {
private const val DEFAULT_ALPHA_FROM = 0f
}
}
\ No newline at end of file
package com.chad.library.adapter.base.animation
import android.animation.Animator
import android.view.View
/**
* https://github.com/CymChad/BaseRecyclerViewAdapterHelper
*/
interface BaseAnimation {
fun animators(view: View): Array<Animator>
}
\ No newline at end of file
package com.chad.library.adapter.base.animation
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
import android.view.animation.DecelerateInterpolator
/**
* https://github.com/CymChad/BaseRecyclerViewAdapterHelper
*/
class ScaleInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_SCALE_FROM) : BaseAnimation {
override fun animators(view: View): Array<Animator> {
val scaleX = ObjectAnimator.ofFloat(view, "scaleX", mFrom, 1f)
scaleX.duration = 300L
scaleX.interpolator = DecelerateInterpolator()
val scaleY = ObjectAnimator.ofFloat(view, "scaleY", mFrom, 1f)
scaleY.duration = 300L
scaleY.interpolator = DecelerateInterpolator()
return arrayOf(scaleX, scaleY)
}
companion object {
private const val DEFAULT_SCALE_FROM = .5f
}
}
\ No newline at end of file
package com.chad.library.adapter.base.animation
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
import android.view.animation.DecelerateInterpolator
/**
* https://github.com/CymChad/BaseRecyclerViewAdapterHelper
*/
class SlideInBottomAnimation : BaseAnimation {
override fun animators(view: View): Array<Animator> {
val animator = ObjectAnimator.ofFloat(view, "translationY", view.measuredHeight.toFloat(), 0f)
animator.duration = 400L
animator.interpolator = DecelerateInterpolator(1.3f)
return arrayOf(animator)
}
}
\ No newline at end of file
package com.chad.library.adapter.base.animation
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
import android.view.animation.DecelerateInterpolator
/**
* https://github.com/CymChad/BaseRecyclerViewAdapterHelper
*/
class SlideInLeftAnimation : BaseAnimation {
override fun animators(view: View): Array<Animator> {
val animator = ObjectAnimator.ofFloat(view, "translationX", -view.rootView.width.toFloat(), 0f)
animator.duration = 400L
animator.interpolator = DecelerateInterpolator(1.8f)
return arrayOf(animator)
}
}
\ No newline at end of file
package com.chad.library.adapter.base.animation
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
import android.view.animation.DecelerateInterpolator
/**
* https://github.com/CymChad/BaseRecyclerViewAdapterHelper
*/
class SlideInRightAnimation : BaseAnimation {
override fun animators(view: View): Array<Animator> {
val animator = ObjectAnimator.ofFloat(view, "translationX", view.rootView.width.toFloat(), 0f)
animator.duration = 400L
animator.interpolator = DecelerateInterpolator(1.8f)
return arrayOf(animator)
}
}
\ No newline at end of file
package com.chad.library.adapter.base.binder
import android.content.Context
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import com.chad.library.adapter.base.BaseBinderAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
* Binder 的基类
*/
abstract class BaseItemBinder<T, VH : BaseViewHolder> {
private val clickViewIds by lazy(LazyThreadSafetyMode.NONE) { ArrayList<Int>() }
private val longClickViewIds by lazy(LazyThreadSafetyMode.NONE) { ArrayList<Int>() }
internal var _adapter: BaseBinderAdapter? = null
internal var _context: Context? = null
val adapter: BaseBinderAdapter
get() {
checkNotNull(_adapter) {
"""This $this has not been attached to BaseBinderAdapter yet.
You should not call the method before addItemBinder()."""
}
return _adapter!!
}
val context: Context
get() {
checkNotNull(_context) {
"""This $this has not been attached to BaseBinderAdapter yet.
You should not call the method before onCreateViewHolder()."""
}
return _context!!
}
val data: MutableList<Any> get() = adapter.data
abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH
/**
* 在此处对设置item数据
* @param holder VH
* @param data T
*/
abstract fun convert(holder: VH, data: T)
/**
* 使用局部刷新时候,会调用此方法
* @param holder VH
* @param data T
* @param payloads List<Any>
*/
open fun convert(holder: VH, data: T, payloads: List<Any>) {}
open fun onFailedToRecycleView(holder: VH): Boolean {
return false
}
/**
* Called when a view created by this [BaseItemBinder] has been attached to a window.
* 当此[BaseItemBinder]出现在屏幕上的时候,会调用此方法
*
* This can be used as a reasonable signal that the view is about to be seen
* by the user. If the [BaseItemBinder] previously freed any resources in
* [onViewDetachedFromWindow][.onViewDetachedFromWindow]
* those resources should be restored here.
*
* @param holder Holder of the view being attached
*/
open fun onViewAttachedToWindow(holder: VH) {}
/**
* Called when a view created by this [BaseItemBinder] has been detached from its
* window.
* 当此[BaseItemBinder]从屏幕上移除的时候,会调用此方法
*
* Becoming detached from the window is not necessarily a permanent condition;
* the consumer of an Adapter's views may choose to cache views offscreen while they
* are not visible, attaching and detaching them as appropriate.
*
* @param holder Holder of the view being detached
*/
open fun onViewDetachedFromWindow(holder: VH) {}
/**
* item 若想实现条目点击事件则重写该方法
* @param holder VH
* @param data T
* @param position Int
*/
open fun onClick(holder: VH, view: View, data: T, position: Int) {}
/**
* item 若想实现条目长按事件则重写该方法
* @param holder VH
* @param data T
* @param position Int
* @return Boolean
*/
open fun onLongClick(holder: VH, view: View, data: T, position: Int): Boolean {
return false
}
/**
* item 子控件的点击事件
* @param holder VH
* @param view View
* @param data T
* @param position Int
*/
open fun onChildClick(holder: VH, view: View, data: T, position: Int) {}
/**
* item 子控件的长按事件
* @param holder VH
* @param view View
* @param data T
* @param position Int
* @return Boolean
*/
open fun onChildLongClick(holder: VH, view: View, data: T, position: Int): Boolean {
return false
}
fun addChildClickViewIds(@IdRes vararg ids: Int) {
ids.forEach {
this.clickViewIds.add(it)
}
}
fun getChildClickViewIds() = this.clickViewIds
fun addChildLongClickViewIds(@IdRes vararg ids: Int) {
ids.forEach {
this.longClickViewIds.add(it)
}
}
fun getChildLongClickViewIds() = this.longClickViewIds
}
\ No newline at end of file
package com.chad.library.adapter.base.binder
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.ViewDataBinding
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
* 使用 DataBinding 快速构建 Binder
* @param T item数据类型
* @param DB : ViewDataBinding
*/
abstract class QuickDataBindingItemBinder<T, DB : ViewDataBinding> : BaseItemBinder<T, QuickDataBindingItemBinder.BinderDataBindingHolder<DB>>() {
/**
* 此 Holder 不适用于其他 BaseAdapter,仅针对[BaseBinderAdapter]
*/
class BinderDataBindingHolder<DB : ViewDataBinding>(val dataBinding: DB) : BaseViewHolder(dataBinding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BinderDataBindingHolder<DB> {
return BinderDataBindingHolder(onCreateDataBinding(LayoutInflater.from(parent.context), parent, viewType))
}
abstract fun onCreateDataBinding(layoutInflater: LayoutInflater, parent: ViewGroup, viewType: Int): DB
}
\ No newline at end of file
package com.chad.library.adapter.base.binder
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import com.chad.library.adapter.base.util.getItemView
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
* 使用布局 ID 快速构建 Binder
* @param T item 数据类型
*/
abstract class QuickItemBinder<T> : BaseItemBinder<T, BaseViewHolder>() {
@LayoutRes
abstract fun getLayoutId(): Int
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder =
BaseViewHolder(parent.getItemView(getLayoutId()))
}
package com.chad.library.adapter.base.binder
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
* 使用 ViewBinding 快速构建 Binder
* @param T item数据类型
* @param VB : ViewBinding
*/
abstract class QuickViewBindingItemBinder<T, VB : ViewBinding> : BaseItemBinder<T, QuickViewBindingItemBinder.BinderVBHolder<VB>>() {
/**
* 此 Holder 不适用于其他 BaseAdapter,仅针对[BaseBinderAdapter]
*/
class BinderVBHolder<VB : ViewBinding>(val viewBinding: VB) : BaseViewHolder(viewBinding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BinderVBHolder<VB> {
return BinderVBHolder(onCreateViewBinding(LayoutInflater.from(parent.context), parent, viewType))
}
abstract fun onCreateViewBinding(layoutInflater: LayoutInflater, parent: ViewGroup, viewType: Int): VB
}
\ No newline at end of file
package com.chad.library.adapter.base.delegate
import android.util.SparseIntArray
import androidx.annotation.LayoutRes
/**
* help you to achieve multi type easily
*
*
* Created by tysheng
* Date: 2017/4/6 08:41.
* Email: tyshengsx@gmail.com
*
* more information: https://github.com/CymChad/BaseRecyclerViewAdapterHelper/issues/968
*/
abstract class BaseMultiTypeDelegate<T>(private var layouts: SparseIntArray = SparseIntArray()) {
private var autoMode: Boolean = false
private var selfMode: Boolean = false
/**
* get the item type from specific entity.
*
* @param data entity
* @param position
* @return item type
*/
abstract fun getItemType(data: List<T>, position: Int): Int
fun getLayoutId(viewType: Int): Int {
val layoutResId = layouts.get(viewType)
require(layoutResId != 0) { "ViewType: $viewType found layoutResId,please use registerItemType() first!" }
return layoutResId
}
private fun registerItemType(type: Int, @LayoutRes layoutResId: Int) {
this.layouts.put(type, layoutResId)
}
/**
* auto increase type vale, start from 0.
*
* @param layoutResIds layout id arrays
* @return MultiTypeDelegate
*/
fun addItemTypeAutoIncrease(@LayoutRes vararg layoutResIds: Int): BaseMultiTypeDelegate<T> {
autoMode = true
checkMode(selfMode)
for (i in layoutResIds.indices) {
registerItemType(i, layoutResIds[i])
}
return this
}
/**
* set your own type one by one.
*
* @param type type value
* @param layoutResId layout id
* @return MultiTypeDelegate
*/
fun addItemType(type: Int, @LayoutRes layoutResId: Int): BaseMultiTypeDelegate<T> {
selfMode = true
checkMode(autoMode)
registerItemType(type, layoutResId)
return this
}
private fun checkMode(mode: Boolean) {
require(!mode) { "Don't mess two register mode" }
}
}
package com.chad.library.adapter.base.diff
import android.os.Handler
import android.os.Looper
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.DiffUtil.DiffResult
import androidx.recyclerview.widget.ListUpdateCallback
import com.chad.library.adapter.base.BaseQuickAdapter
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executor
class BrvahAsyncDiffer<T>(private val adapter: BaseQuickAdapter<T, *>,
private val config: BrvahAsyncDifferConfig<T>) : DifferImp<T> {
private val mUpdateCallback: ListUpdateCallback = BrvahListUpdateCallback(adapter)
private var mMainThreadExecutor: Executor
private class MainThreadExecutor internal constructor() : Executor {
val mHandler = Handler(Looper.getMainLooper())
override fun execute(command: Runnable) {
mHandler.post(command)
}
}
private val sMainThreadExecutor: Executor = MainThreadExecutor()
init {
mMainThreadExecutor = config.mainThreadExecutor ?: sMainThreadExecutor
}
private val mListeners: MutableList<ListChangeListener<T>> = CopyOnWriteArrayList()
private var mMaxScheduledGeneration = 0
fun addData(index: Int, data: T) {
val previousList: List<T> = adapter.data
adapter.data.add(index, data)
// final List<T> previousList = mReadOnlyList;
// mReadOnlyList = Collections.unmodifiableList(adapterData);
mUpdateCallback.onInserted(index, 1)
onCurrentListChanged(previousList, null)
}
fun addData(data: T) {
val previousList: List<T> = adapter.data
adapter.data.add(data)
mUpdateCallback.onInserted(previousList.size, 1)
onCurrentListChanged(previousList, null)
}
fun addList(list: List<T>?) {
if (list == null) return
val previousList: List<T> = adapter.data
adapter.data.addAll(list)
// final List<T> previousList = mReadOnlyList;
// mReadOnlyList = Collections.unmodifiableList(adapterData);
mUpdateCallback.onInserted(previousList.size, list.size)
onCurrentListChanged(previousList, null)
}
/**
* 改变某一个数据
*/
fun changeData(index: Int, newData: T, payload: T?) {
val previousList: List<T> = adapter.data
adapter.data[index] = newData
// final List<T> previousList = mReadOnlyList;
// mReadOnlyList = Collections.unmodifiableList(adapterData);
mUpdateCallback.onChanged(index, 1, payload)
onCurrentListChanged(previousList, null)
}
/**
* 移除某一个数据
*/
fun removeAt(index: Int) {
val previousList: List<T> = adapter.data
adapter.data.removeAt(index)
// final List<T> previousList = mReadOnlyList;
// mReadOnlyList = Collections.unmodifiableList(adapterData);
mUpdateCallback.onRemoved(index, 1)
onCurrentListChanged(previousList, null)
}
fun remove(t: T) {
val previousList: List<T> = adapter.data
val index = adapter.data.indexOf(t)
if (index == -1) return
adapter.data.removeAt(index)
// final List<T> previousList = mReadOnlyList;
// mReadOnlyList = Collections.unmodifiableList(adapterData);
mUpdateCallback.onRemoved(index, 1)
onCurrentListChanged(previousList, null)
}
@JvmOverloads
fun submitList(newList: MutableList<T>?, commitCallback: Runnable? = null) {
// incrementing generation means any currently-running diffs are discarded when they finish
val runGeneration: Int = ++mMaxScheduledGeneration
if (newList === adapter.data) {
// nothing to do (Note - still had to inc generation, since may have ongoing work)
commitCallback?.run()
return
}
val oldList: List<T> = adapter.data
// fast simple remove all
if (newList == null) {
val countRemoved: Int = adapter.data.size
adapter.data = arrayListOf()
// notify last, after list is updated
mUpdateCallback.onRemoved(0, countRemoved)
onCurrentListChanged(oldList, commitCallback)
return
}
// fast simple first insert
if (adapter.data.isEmpty()) {
adapter.data = newList
// notify last, after list is updated
mUpdateCallback.onInserted(0, newList.size)
onCurrentListChanged(oldList, commitCallback)
return
}
config.backgroundThreadExecutor.execute {
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldList.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem: T? = oldList[oldItemPosition]
val newItem: T? = newList[newItemPosition]
return if (oldItem != null && newItem != null) {
config.diffCallback.areItemsTheSame(oldItem, newItem)
} else oldItem == null && newItem == null
// If both items are null we consider them the same.
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem: T? = oldList[oldItemPosition]
val newItem: T? = newList[newItemPosition]
if (oldItem != null && newItem != null) {
return config.diffCallback.areContentsTheSame(oldItem, newItem)
}
if (oldItem == null && newItem == null) {
return true
}
throw AssertionError()
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val oldItem: T? = oldList[oldItemPosition]
val newItem: T? = newList[newItemPosition]
if (oldItem != null && newItem != null) {
return config.diffCallback.getChangePayload(oldItem, newItem)
}
throw AssertionError()
}
})
mMainThreadExecutor.execute {
if (mMaxScheduledGeneration == runGeneration) {
latchList(newList, result, commitCallback)
}
}
}
}
private fun latchList(
newList: MutableList<T>,
diffResult: DiffResult,
commitCallback: Runnable?) {
val previousList: List<T> = adapter.data
adapter.data = newList
diffResult.dispatchUpdatesTo(mUpdateCallback)
onCurrentListChanged(previousList, commitCallback)
}
private fun onCurrentListChanged(previousList: List<T>,
commitCallback: Runnable?) {
for (listener in mListeners) {
listener.onCurrentListChanged(previousList, adapter.data)
}
commitCallback?.run()
}
/**
* Add a ListListener to receive updates when the current List changes.
*
* @param listener Listener to receive updates.
*
* @see .getCurrentList
* @see .removeListListener
*/
override fun addListListener(listener: ListChangeListener<T>) {
mListeners.add(listener)
}
/**
* Remove a previously registered ListListener.
*
* @param listener Previously registered listener.
* @see .getCurrentList
* @see .addListListener
*/
fun removeListListener(listener: ListChangeListener<T>) {
mListeners.remove(listener)
}
fun clearAllListListener() {
mListeners.clear()
}
}
\ No newline at end of file
package com.chad.library.adapter.base.diff
import androidx.annotation.RestrictTo
import androidx.recyclerview.widget.DiffUtil
import java.util.concurrent.Executor
import java.util.concurrent.Executors
class BrvahAsyncDifferConfig<T>(
@RestrictTo(RestrictTo.Scope.LIBRARY)
val mainThreadExecutor: Executor?,
val backgroundThreadExecutor: Executor,
val diffCallback: DiffUtil.ItemCallback<T>) {
/**
* Builder class for [BrvahAsyncDifferConfig].
*
* @param <T>
</T> */
class Builder<T>(private val mDiffCallback: DiffUtil.ItemCallback<T>) {
private var mMainThreadExecutor: Executor? = null
private var mBackgroundThreadExecutor: Executor? = null
/**
* If provided, defines the main thread executor used to dispatch adapter update
* notifications on the main thread.
*
*
* If not provided, it will default to the main thread.
*
* @param executor The executor which can run tasks in the UI thread.
* @return this
*
* @hide
*/
fun setMainThreadExecutor(executor: Executor?): Builder<T> {
mMainThreadExecutor = executor
return this
}
/**
* If provided, defines the background executor used to calculate the diff between an old
* and a new list.
*
*
* If not provided, defaults to two thread pool executor, shared by all ListAdapterConfigs.
*
* @param executor The background executor to run list diffing.
* @return this
*/
fun setBackgroundThreadExecutor(executor: Executor?): Builder<T> {
mBackgroundThreadExecutor = executor
return this
}
/**
* Creates a [BrvahAsyncDifferConfig] with the given parameters.
*
* @return A new AsyncDifferConfig.
*/
fun build(): BrvahAsyncDifferConfig<T> {
if (mBackgroundThreadExecutor == null) {
synchronized(sExecutorLock) {
if (sDiffExecutor == null) {
sDiffExecutor = Executors.newFixedThreadPool(2)
}
}
mBackgroundThreadExecutor = sDiffExecutor
}
return BrvahAsyncDifferConfig(
mMainThreadExecutor,
mBackgroundThreadExecutor!!,
mDiffCallback)
}
companion object {
// TODO: remove the below once supportlib has its own appropriate executors
private val sExecutorLock = Any()
private var sDiffExecutor: Executor? = null
}
}
}
\ No newline at end of file
package com.chad.library.adapter.base.diff
import androidx.recyclerview.widget.ListUpdateCallback
import com.chad.library.adapter.base.BaseQuickAdapter
class BrvahListUpdateCallback(private val mAdapter: BaseQuickAdapter<*, *>) : ListUpdateCallback {
override fun onInserted(position: Int, count: Int) {
mAdapter.notifyItemRangeInserted(position + mAdapter.headerLayoutCount, count)
}
override fun onRemoved(position: Int, count: Int) {
if (mAdapter.mLoadMoreModule?.hasLoadMoreView() == true && mAdapter.itemCount == 0) {
// 如果注册了加载更多,并且当前itemCount为0,则需要加上loadMore所占用的一行
mAdapter.notifyItemRangeRemoved(position + mAdapter.headerLayoutCount, count + 1)
} else {
mAdapter.notifyItemRangeRemoved(position + mAdapter.headerLayoutCount, count)
}
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
mAdapter.notifyItemMoved(fromPosition + mAdapter.headerLayoutCount, toPosition + mAdapter.headerLayoutCount)
}
override fun onChanged(position: Int, count: Int, payload: Any?) {
mAdapter.notifyItemRangeChanged(position + mAdapter.headerLayoutCount, count, payload)
}
}
\ No newline at end of file
package com.chad.library.adapter.base.diff;
import androidx.annotation.NonNull;
/**
* 使用java接口定义方法
* @param <T>
*/
public interface DifferImp<T> {
void addListListener(@NonNull ListChangeListener<T> listChangeListener);
}
package com.chad.library.adapter.base.diff;
import androidx.annotation.NonNull;
import java.util.List;
public interface ListChangeListener<T> {
/**
* Called after the current List has been updated.
*
* @param previousList The previous list.
* @param currentList The new current list.
*/
void onCurrentListChanged(@NonNull List<T> previousList, @NonNull List<T> currentList);
}
package com.chad.library.adapter.base.dragswipe;
import android.graphics.Canvas;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.chad.library.R;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.module.BaseDraggableModule;
/**
* @author luoxw
* @date 2016/6/20
*/
public class DragAndSwipeCallback extends ItemTouchHelper.Callback {
private BaseDraggableModule mDraggableModule;
private float mMoveThreshold = 0.1f;
private float mSwipeThreshold = 0.7f;
private int mDragMoveFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
private int mSwipeMoveFlags = ItemTouchHelper.END;
public DragAndSwipeCallback(BaseDraggableModule draggableModule) {
mDraggableModule = draggableModule;
}
@Override
public boolean isLongPressDragEnabled() {
if (mDraggableModule != null) {
return mDraggableModule.isDragEnabled() && !mDraggableModule.hasToggleView();
}
return false;
}
@Override
public boolean isItemViewSwipeEnabled() {
if (mDraggableModule != null) {
return mDraggableModule.isSwipeEnabled();
}
return false;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG
&& !isViewCreateByAdapter(viewHolder)) {
if (mDraggableModule != null) {
mDraggableModule.onItemDragStart(viewHolder);
}
viewHolder.itemView.setTag(R.id.BaseQuickAdapter_dragging_support, true);
} else if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE
&& !isViewCreateByAdapter(viewHolder)) {
if (mDraggableModule != null) {
mDraggableModule.onItemSwipeStart(viewHolder);
}
viewHolder.itemView.setTag(R.id.BaseQuickAdapter_swiping_support, true);
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (isViewCreateByAdapter(viewHolder)) {
return;
}
if (viewHolder.itemView.getTag(R.id.BaseQuickAdapter_dragging_support) != null
&& (Boolean) viewHolder.itemView.getTag(R.id.BaseQuickAdapter_dragging_support)) {
if (mDraggableModule != null) {
mDraggableModule.onItemDragEnd(viewHolder);
}
viewHolder.itemView.setTag(R.id.BaseQuickAdapter_dragging_support, false);
}
if (viewHolder.itemView.getTag(R.id.BaseQuickAdapter_swiping_support) != null
&& (Boolean) viewHolder.itemView.getTag(R.id.BaseQuickAdapter_swiping_support)) {
if (mDraggableModule != null) {
mDraggableModule.onItemSwipeClear(viewHolder);
}
viewHolder.itemView.setTag(R.id.BaseQuickAdapter_swiping_support, false);
}
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
if (isViewCreateByAdapter(viewHolder)) {
return makeMovementFlags(0, 0);
}
return makeMovementFlags(mDragMoveFlags, mSwipeMoveFlags);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder source, @NonNull RecyclerView.ViewHolder target) {
return source.getItemViewType() == target.getItemViewType();
}
@Override
public void onMoved(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder source, int fromPos, @NonNull RecyclerView.ViewHolder target, int toPos, int x, int y) {
super.onMoved(recyclerView, source, fromPos, target, toPos, x, y);
if (mDraggableModule != null) {
mDraggableModule.onItemDragMoving(source, target);
}
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (!isViewCreateByAdapter(viewHolder)) {
if (mDraggableModule != null) {
mDraggableModule.onItemSwiped(viewHolder);
}
}
}
@Override
public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
return mMoveThreshold;
}
@Override
public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
return mSwipeThreshold;
}
/**
* Set the fraction that the user should move the View to be considered as swiped.
* The fraction is calculated with respect to RecyclerView's bounds.
* <p>
* Default value is .5f, which means, to swipe a View, user must move the View at least
* half of RecyclerView's width or height, depending on the swipe direction.
*
* @param swipeThreshold A float value that denotes the fraction of the View size. Default value
* is .8f .
*/
public void setSwipeThreshold(float swipeThreshold) {
mSwipeThreshold = swipeThreshold;
}
/**
* Set the fraction that the user should move the View to be considered as it is
* dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views
* below it for a possible drop.
*
* @param moveThreshold A float value that denotes the fraction of the View size. Default value is
* .1f .
*/
public void setMoveThreshold(float moveThreshold) {
mMoveThreshold = moveThreshold;
}
/**
* <p>Set the drag movement direction.</p>
* <p>The value should be ItemTouchHelper.UP, ItemTouchHelper.DOWN, ItemTouchHelper.LEFT, ItemTouchHelper.RIGHT or their combination.</p>
* You can combine them like ItemTouchHelper.UP | ItemTouchHelper.DOWN, it means that the item could only move up and down when dragged.
*
* @param dragMoveFlags the drag movement direction. Default value is ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT.
*/
public void setDragMoveFlags(int dragMoveFlags) {
mDragMoveFlags = dragMoveFlags;
}
/**
* <p>Set the swipe movement direction.</p>
* <p>The value should be ItemTouchHelper.START, ItemTouchHelper.END or their combination.</p>
* You can combine them like ItemTouchHelper.START | ItemTouchHelper.END, it means that the item could swipe to both left or right.
*
* @param swipeMoveFlags the swipe movement direction. Default value is ItemTouchHelper.END.
*/
public void setSwipeMoveFlags(int swipeMoveFlags) {
mSwipeMoveFlags = swipeMoveFlags;
}
@Override
public void onChildDrawOver(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE
&& !isViewCreateByAdapter(viewHolder)) {
View itemView = viewHolder.itemView;
c.save();
if (dX > 0) {
c.clipRect(itemView.getLeft(), itemView.getTop(),
itemView.getLeft() + dX, itemView.getBottom());
c.translate(itemView.getLeft(), itemView.getTop());
} else {
c.clipRect(itemView.getRight() + dX, itemView.getTop(),
itemView.getRight(), itemView.getBottom());
c.translate(itemView.getRight() + dX, itemView.getTop());
}
if (mDraggableModule != null) {
mDraggableModule.onItemSwiping(c, viewHolder, dX, dY, isCurrentlyActive);
}
c.restore();
}
}
private boolean isViewCreateByAdapter(@NonNull RecyclerView.ViewHolder viewHolder) {
int type = viewHolder.getItemViewType();
return type == BaseQuickAdapter.HEADER_VIEW || type == BaseQuickAdapter.LOAD_MORE_VIEW
|| type == BaseQuickAdapter.FOOTER_VIEW || type == BaseQuickAdapter.EMPTY_VIEW;
}
}
package com.chad.library.adapter.base.entity;
/**
* 仅供java使用
*
* 由于java无法实现{@link SectionEntity}中的默认接口实现,所以使用抽象类再封装一次,用于提供默认实现。
*/
public abstract class JSectionEntity implements SectionEntity {
/**
* 用于返回item类型,除了头布局外,默认只有 NORMAL_TYPE 一种布局
* 如果需要实现 item 多布局,请重写此方法,返回自己的type
*/
@Override
public int getItemType() {
if (isHeader()) {
return SectionEntity.Companion.HEADER_TYPE;
} else {
// 拷贝 重写此处,返回自己的多布局类型
return SectionEntity.Companion.NORMAL_TYPE;
}
}
}
package com.chad.library.adapter.base.entity
/**
* 多布局类型
*/
interface MultiItemEntity {
val itemType: Int
}
package com.chad.library.adapter.base.entity
/**
* 带头部布局的实体类接口
* 实体类请继承此接口;如果使用java,请使用[JSectionEntity]抽象类
*/
interface SectionEntity : MultiItemEntity {
val isHeader: Boolean
/**
* 用于返回item类型,除了头布局外,默认只有[NORMAL_TYPE]一种布局
* 如果需要实现 item 多布局,请重写此方法,返回自己的type
*/
override val itemType: Int
get() = if (isHeader) HEADER_TYPE else NORMAL_TYPE
companion object {
const val NORMAL_TYPE = -100
const val HEADER_TYPE = -99
}
}
package com.chad.library.adapter.base.entity.node
abstract class BaseExpandNode : BaseNode() {
var isExpanded: Boolean = true
}
\ No newline at end of file
package com.chad.library.adapter.base.entity.node
abstract class BaseNode {
/**
* 重写此方法,获取子节点。如果没有子节点,返回 null 或者 空数组
*
* 如果返回 null,则无法对子节点的数据进行新增和删除等操作
*/
abstract val childNode: MutableList<BaseNode>?
}
\ No newline at end of file
package com.chad.library.adapter.base.entity.node
/**
* 如果需要,可以实现此接口,返回脚部节点
*/
interface NodeFooterImp {
/**
* 返回脚部节点
* @return BaseNode? 如果返回 null,则代表没有脚部节点
*/
val footerNode: BaseNode?
}
\ No newline at end of file
package com.chad.library.adapter.base.listener;
import androidx.annotation.Nullable;
/**
* @author: limuyang
* @date: 2019-12-05
* @Description:
*/
public interface DraggableListenerImp {
void setOnItemDragListener(@Nullable OnItemDragListener onItemDragListener);
void setOnItemSwipeListener(@Nullable OnItemSwipeListener onItemSwipeListener);
}
package com.chad.library.adapter.base.listener;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
/**
* @author: limuyang
* @date: 2019-12-03
* @Description:
*/
public interface GridSpanSizeLookup {
int getSpanSize(@NonNull GridLayoutManager gridLayoutManager, int viewType, int position);
}
package com.chad.library.adapter.base.listener;
import androidx.annotation.Nullable;
/**
* @author: limuyang
* @date: 2019-12-03
* @Description: LoadMore需要设置的接口。使用java定义,以兼容java写法
*/
public interface LoadMoreListenerImp {
void setOnLoadMoreListener(@Nullable OnLoadMoreListener listener);
}
package com.chad.library.adapter.base.listener;
import android.view.View;
import androidx.annotation.NonNull;
import com.chad.library.adapter.base.BaseQuickAdapter;
/**
* @author: limuyang
* @date: 2019-12-03
* @Description:
*/
public interface OnItemChildClickListener {
/**
* callback method to be invoked when an item child in this view has been click
*
* @param adapter BaseQuickAdapter
* @param view The view whihin the ItemView that was clicked
* @param position The position of the view int the adapter
*/
void onItemChildClick(@NonNull BaseQuickAdapter<?,?> adapter, @NonNull View view, int position);
}
package com.chad.library.adapter.base.listener;
import android.view.View;
import androidx.annotation.NonNull;
import com.chad.library.adapter.base.BaseQuickAdapter;
/**
* @author: limuyang
* @date: 2019-12-03
* @Description:
*/
public interface OnItemChildLongClickListener {
/**
* callback method to be invoked when an item in this view has been
* click and held
*
* @param adapter this BaseQuickAdapter adapter
* @param view The childView whihin the itemView that was clicked and held.
* @param position The position of the view int the adapter
* @return true if the callback consumed the long click ,false otherwise
*/
boolean onItemChildLongClick(@NonNull BaseQuickAdapter<?,?> adapter, @NonNull View view, int position);
}
package com.chad.library.adapter.base.listener;
import android.view.View;
import androidx.annotation.NonNull;
import com.chad.library.adapter.base.BaseQuickAdapter;
/**
* @author: limuyang
* @date: 2019-12-03
* @Description: Interface definition for a callback to be invoked when an item in this
* RecyclerView itemView has been clicked.
*/
public interface OnItemClickListener {
/**
* Callback method to be invoked when an item in this RecyclerView has
* been clicked.
*
* @param adapter the adapter
* @param view The itemView within the RecyclerView that was clicked (this
* will be a view provided by the adapter)
* @param position The position of the view in the adapter.
*/
void onItemClick(@NonNull BaseQuickAdapter<?,?> adapter, @NonNull View view, int position);
}
package com.chad.library.adapter.base.listener;
import androidx.recyclerview.widget.RecyclerView;
/**
* Created by luoxw on 2016/6/20.
*/
public interface OnItemDragListener {
void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos);
void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to);
void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos);
}
package com.chad.library.adapter.base.listener;
import android.view.View;
import androidx.annotation.NonNull;
import com.chad.library.adapter.base.BaseQuickAdapter;
/**
* @author: limuyang
* @date: 2019-12-03
* @Description:
*/
public interface OnItemLongClickListener {
/**
* callback method to be invoked when an item in this view has been
* click and held
*
* @param adapter the adapter
* @param view The view whihin the RecyclerView that was clicked and held.
* @param position The position of the view int the adapter
* @return true if the callback consumed the long click ,false otherwise
*/
boolean onItemLongClick(@NonNull BaseQuickAdapter<?,?> adapter, @NonNull View view, int position);
}
package com.chad.library.adapter.base.listener;
import android.graphics.Canvas;
import androidx.recyclerview.widget.RecyclerView;
/**
* Created by luoxw on 2016/6/23.
*/
public interface OnItemSwipeListener {
/**
* Called when the swipe action start.
*/
void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos);
/**
* Called when the swipe action is over.
* If you change the view on the start, you should reset is here, no matter the item has swiped or not.
*
* @param pos If the view is swiped, pos will be negative.
*/
void clearView(RecyclerView.ViewHolder viewHolder, int pos);
/**
* Called when item is swiped, the view is going to be removed from the adapter.
*/
void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos);
/**
* Draw on the empty edge when swipe moving
*
* @param canvas the empty edge's canvas
* @param viewHolder The ViewHolder which is being interacted by the User or it was
* interacted and simply animating to its original position
* @param dX The amount of horizontal displacement caused by user's action
* @param dY The amount of vertical displacement caused by user's action
* @param isCurrentlyActive True if this view is currently being controlled by the user or
* false it is simply animating back to its original state.
*/
void onItemSwipeMoving(Canvas canvas, RecyclerView.ViewHolder viewHolder, float dX, float dY, boolean isCurrentlyActive);
}
package com.chad.library.adapter.base.listener;
/**
* @author: limuyang
* @date: 2019-12-03
* @Description:
*/
public interface OnLoadMoreListener {
void onLoadMore();
}
package com.chad.library.adapter.base.listener;
/**
* @author: limuyang
* @date: 2019-12-03
* @Description:
*/
public interface OnUpFetchListener {
void onUpFetch();
}
package com.chad.library.adapter.base.listener;
import androidx.annotation.Nullable;
/**
* @author: limuyang
* @date: 2019-12-03
* @Description: UpFetch需要设置的接口。使用java定义,以兼容java写法
*/
public interface UpFetchListenerImp {
void setOnUpFetchListener(@Nullable OnUpFetchListener listener);
}
package com.chad.library.adapter.base.loadmore
import android.view.View
import android.view.ViewGroup
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
*
* @author limuyang
*/
enum class LoadMoreStatus {
Complete, Loading, Fail, End
}
/**
* 继承此类,实行自定义loadMore视图
*/
abstract class BaseLoadMoreView {
/**
* 根布局
* @param parent ViewGroup
* @return View
*/
abstract fun getRootView(parent: ViewGroup): View
/**
* 布局中的 加载更多视图
* @param holder BaseViewHolder
* @return View
*/
abstract fun getLoadingView(holder: BaseViewHolder): View
/**
* 布局中的 加载完成布局
* @param holder BaseViewHolder
* @return View
*/
abstract fun getLoadComplete(holder: BaseViewHolder): View
/**
* 布局中的 加载结束布局
* @param holder BaseViewHolder
* @return View
*/
abstract fun getLoadEndView(holder: BaseViewHolder): View
/**
* 布局中的 加载失败布局
* @param holder BaseViewHolder
* @return View
*/
abstract fun getLoadFailView(holder: BaseViewHolder): View
/**
* 可重写此方式,实行自定义逻辑
* @param holder BaseViewHolder
* @param position Int
* @param loadMoreStatus LoadMoreStatus
*/
open fun convert(holder: BaseViewHolder, position: Int, loadMoreStatus: LoadMoreStatus) {
when (loadMoreStatus) {
LoadMoreStatus.Complete -> {
getLoadingView(holder).isVisible(false)
getLoadComplete(holder).isVisible(true)
getLoadFailView(holder).isVisible(false)
getLoadEndView(holder).isVisible(false)
}
LoadMoreStatus.Loading -> {
getLoadingView(holder).isVisible(true)
getLoadComplete(holder).isVisible(false)
getLoadFailView(holder).isVisible(false)
getLoadEndView(holder).isVisible(false)
}
LoadMoreStatus.Fail -> {
getLoadingView(holder).isVisible(false)
getLoadComplete(holder).isVisible(false)
getLoadFailView(holder).isVisible(true)
getLoadEndView(holder).isVisible(false)
}
LoadMoreStatus.End -> {
getLoadingView(holder).isVisible(false)
getLoadComplete(holder).isVisible(false)
getLoadFailView(holder).isVisible(false)
getLoadEndView(holder).isVisible(true)
}
}
}
private fun View.isVisible(visible: Boolean) {
this.visibility = if (visible) {
View.VISIBLE
} else {
View.GONE
}
}
}
package com.chad.library.adapter.base.loadmore
import android.view.View
import android.view.ViewGroup
import com.chad.library.R
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import com.chad.library.adapter.base.util.getItemView
class SimpleLoadMoreView : BaseLoadMoreView() {
override fun getRootView(parent: ViewGroup): View =
parent.getItemView(R.layout.brvah_quick_view_load_more)
override fun getLoadingView(holder: BaseViewHolder): View =
holder.getView(R.id.load_more_loading_view)
override fun getLoadComplete(holder: BaseViewHolder): View =
holder.getView(R.id.load_more_load_complete_view)
override fun getLoadEndView(holder: BaseViewHolder): View =
holder.getView(R.id.load_more_load_end_view)
override fun getLoadFailView(holder: BaseViewHolder): View =
holder.getView(R.id.load_more_load_fail_view)
}
\ No newline at end of file
package com.chad.library.adapter.base.module
import android.graphics.Canvas
import android.view.MotionEvent
import android.view.View
import android.view.View.OnLongClickListener
import android.view.View.OnTouchListener
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.chad.library.R
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.dragswipe.DragAndSwipeCallback
import com.chad.library.adapter.base.listener.DraggableListenerImp
import com.chad.library.adapter.base.listener.OnItemDragListener
import com.chad.library.adapter.base.listener.OnItemSwipeListener
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import java.util.*
/**
* @author: limuyang
* @date: 2019-12-05
* @Description:
*/
/**
* 需要【拖拽】功能的,[BaseQuickAdapter]继承此接口
*/
interface DraggableModule {
/**
* 重写此方法,返回自定义模块
* @param baseQuickAdapter BaseQuickAdapter<*, *>
* @return BaseExpandableModule
*/
fun addDraggableModule(baseQuickAdapter: BaseQuickAdapter<*, *>): BaseDraggableModule {
return BaseDraggableModule(baseQuickAdapter)
}
}
open class BaseDraggableModule(private val baseQuickAdapter: BaseQuickAdapter<*, *>) : DraggableListenerImp {
var isDragEnabled = false
var isSwipeEnabled = false
var toggleViewId = NO_TOGGLE_VIEW
lateinit var itemTouchHelper: ItemTouchHelper
lateinit var itemTouchHelperCallback: DragAndSwipeCallback
protected var mOnToggleViewTouchListener: OnTouchListener? = null
protected var mOnToggleViewLongClickListener: OnLongClickListener? = null
protected var mOnItemDragListener: OnItemDragListener? = null
protected var mOnItemSwipeListener: OnItemSwipeListener? = null
init {
initItemTouch()
}
private fun initItemTouch() {
itemTouchHelperCallback = DragAndSwipeCallback(this)
itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback)
}
internal fun initView(holder: BaseViewHolder) {
if (isDragEnabled) {
if (hasToggleView()) {
val toggleView = holder.itemView.findViewById<View>(toggleViewId)
if (toggleView != null) {
toggleView.setTag(R.id.BaseQuickAdapter_viewholder_support, holder)
if (isDragOnLongPressEnabled) {
toggleView.setOnLongClickListener(mOnToggleViewLongClickListener)
} else {
toggleView.setOnTouchListener(mOnToggleViewTouchListener)
}
}
}
}
}
fun attachToRecyclerView(recyclerView: RecyclerView) {
itemTouchHelper.attachToRecyclerView(recyclerView)
}
/**
* Is there a toggle view which will trigger drag event.
*/
open fun hasToggleView(): Boolean {
return toggleViewId != NO_TOGGLE_VIEW
}
/**
* Set the drag event should be trigger on long press.
* Work when the toggleViewId has been set.
*
*/
open var isDragOnLongPressEnabled = true
set(value) {
field = value
if (value) {
mOnToggleViewTouchListener = null
mOnToggleViewLongClickListener = OnLongClickListener { v ->
if (isDragEnabled) {
itemTouchHelper.startDrag(v.getTag(R.id.BaseQuickAdapter_viewholder_support) as RecyclerView.ViewHolder)
}
true
}
} else {
mOnToggleViewTouchListener = OnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_DOWN && !isDragOnLongPressEnabled) {
if (isDragEnabled) {
itemTouchHelper.startDrag(v.getTag(R.id.BaseQuickAdapter_viewholder_support) as RecyclerView.ViewHolder)
}
true
} else {
false
}
}
mOnToggleViewLongClickListener = null
}
}
protected fun getViewHolderPosition(viewHolder: RecyclerView.ViewHolder): Int {
return viewHolder.adapterPosition - baseQuickAdapter.headerLayoutCount
}
/************************* Drag *************************/
open fun onItemDragStart(viewHolder: RecyclerView.ViewHolder) {
mOnItemDragListener?.onItemDragStart(viewHolder, getViewHolderPosition(viewHolder))
}
open fun onItemDragMoving(source: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder) {
val from = getViewHolderPosition(source)
val to = getViewHolderPosition(target)
if (inRange(from) && inRange(to)) {
if (from < to) {
for (i in from until to) {
Collections.swap(baseQuickAdapter.data, i, i + 1)
}
} else {
for (i in from downTo to + 1) {
Collections.swap(baseQuickAdapter.data, i, i - 1)
}
}
baseQuickAdapter.notifyItemMoved(source.adapterPosition, target.adapterPosition)
}
mOnItemDragListener?.onItemDragMoving(source, from, target, to)
}
open fun onItemDragEnd(viewHolder: RecyclerView.ViewHolder) {
mOnItemDragListener?.onItemDragEnd(viewHolder, getViewHolderPosition(viewHolder))
}
/************************* Swipe *************************/
open fun onItemSwipeStart(viewHolder: RecyclerView.ViewHolder) {
if (isSwipeEnabled) {
mOnItemSwipeListener?.onItemSwipeStart(viewHolder, getViewHolderPosition(viewHolder))
}
}
open fun onItemSwipeClear(viewHolder: RecyclerView.ViewHolder) {
if (isSwipeEnabled) {
mOnItemSwipeListener?.clearView(viewHolder, getViewHolderPosition(viewHolder))
}
}
open fun onItemSwiped(viewHolder: RecyclerView.ViewHolder) {
val pos = getViewHolderPosition(viewHolder)
if (inRange(pos)) {
baseQuickAdapter.data.removeAt(pos)
baseQuickAdapter.notifyItemRemoved(viewHolder.adapterPosition)
if (isSwipeEnabled) {
mOnItemSwipeListener?.onItemSwiped(viewHolder, pos)
}
}
}
open fun onItemSwiping(canvas: Canvas?, viewHolder: RecyclerView.ViewHolder?, dX: Float, dY: Float, isCurrentlyActive: Boolean) {
if (isSwipeEnabled) {
mOnItemSwipeListener?.onItemSwipeMoving(canvas, viewHolder, dX, dY, isCurrentlyActive)
}
}
private fun inRange(position: Int): Boolean {
return position >= 0 && position < baseQuickAdapter.data.size
}
/**
* 设置监听
* @param onItemDragListener OnItemDragListener?
*/
override fun setOnItemDragListener(onItemDragListener: OnItemDragListener?) {
this.mOnItemDragListener = onItemDragListener
}
override fun setOnItemSwipeListener(onItemSwipeListener: OnItemSwipeListener?) {
this.mOnItemSwipeListener = onItemSwipeListener
}
companion object {
private const val NO_TOGGLE_VIEW = 0
}
}
\ No newline at end of file
package com.chad.library.adapter.base.module
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.listener.LoadMoreListenerImp
import com.chad.library.adapter.base.listener.OnLoadMoreListener
import com.chad.library.adapter.base.loadmore.BaseLoadMoreView
import com.chad.library.adapter.base.loadmore.LoadMoreStatus
import com.chad.library.adapter.base.loadmore.SimpleLoadMoreView
import com.chad.library.adapter.base.viewholder.BaseViewHolder
/**
* @author: limuyang
* @date: 2019-11-29
* @Description: 向下加载更多
*/
/**
* 需要【向下加载更多】功能的,[BaseQuickAdapter]继承此接口
*/
interface LoadMoreModule {
/**
* 重写此方法,返回自定义模块
* @param baseQuickAdapter BaseQuickAdapter<*, *>
* @return BaseLoadMoreModule
*/
fun addLoadMoreModule(baseQuickAdapter: BaseQuickAdapter<*, *>): BaseLoadMoreModule {
return BaseLoadMoreModule(baseQuickAdapter)
}
}
object LoadMoreModuleConfig {
/**
* 设置全局的LodeMoreView
*/
@JvmStatic
var defLoadMoreView: BaseLoadMoreView = SimpleLoadMoreView()
}
/**
* 加载更多基类
*/
open class BaseLoadMoreModule(private val baseQuickAdapter: BaseQuickAdapter<*, *>) : LoadMoreListenerImp {
private var mLoadMoreListener: OnLoadMoreListener? = null
/** 不满一屏时,是否可以继续加载的标记位 */
private var mNextLoadEnable = true
var loadMoreStatus = LoadMoreStatus.Complete
private set
var isLoadEndMoreGone: Boolean = false
private set
/** 设置加载更多布局 */
var loadMoreView = LoadMoreModuleConfig.defLoadMoreView
/** 加载完成后是否允许点击 */
var enableLoadMoreEndClick = false
/** 是否打开自动加载更多 */
var isAutoLoadMore = true
/** 当自动加载开启,同时数据不满一屏时,是否继续执行自动加载更多 */
var isEnableLoadMoreIfNotFullPage = true
/**
* 预加载
*/
var preLoadNumber = 1
set(value) {
if (value > 1) {
field = value
}
}
/**
* 是否加载中
*/
val isLoading: Boolean
get() {
return loadMoreStatus == LoadMoreStatus.Loading
}
/**
* Gets to load more locations
*
* @return
*/
val loadMoreViewPosition: Int
get() {
if (baseQuickAdapter.hasEmptyView()) {
return -1
}
return baseQuickAdapter.let {
it.headerLayoutCount + it.data.size + it.footerLayoutCount
}
}
/**
* 是否打开加载更多
*/
var isEnableLoadMore = false
set(value) {
val oldHasLoadMore = hasLoadMoreView()
field = value
val newHasLoadMore = hasLoadMoreView()
if (oldHasLoadMore) {
if (!newHasLoadMore) {
baseQuickAdapter.notifyItemRemoved(loadMoreViewPosition)
}
} else {
if (newHasLoadMore) {
loadMoreStatus = LoadMoreStatus.Complete
baseQuickAdapter.notifyItemInserted(loadMoreViewPosition)
}
}
}
internal fun setupViewHolder(viewHolder: BaseViewHolder) {
viewHolder.itemView.setOnClickListener {
if (loadMoreStatus == LoadMoreStatus.Fail) {
loadMoreToLoading()
} else if (loadMoreStatus == LoadMoreStatus.Complete) {
loadMoreToLoading()
} else if (enableLoadMoreEndClick && loadMoreStatus == LoadMoreStatus.End) {
loadMoreToLoading()
}
}
}
/**
* The notification starts the callback and loads more
*/
fun loadMoreToLoading() {
if (loadMoreStatus == LoadMoreStatus.Loading) {
return
}
loadMoreStatus = LoadMoreStatus.Loading
baseQuickAdapter.notifyItemChanged(loadMoreViewPosition)
invokeLoadMoreListener()
}
fun hasLoadMoreView(): Boolean {
if (mLoadMoreListener == null || !isEnableLoadMore) {
return false
}
if (loadMoreStatus == LoadMoreStatus.End && isLoadEndMoreGone) {
return false
}
return baseQuickAdapter.data.isNotEmpty()
}
/**
* 自动加载数据
* @param position Int
*/
internal fun autoLoadMore(position: Int) {
if (!isAutoLoadMore) {
//如果不需要自动加载更多,直接返回
return
}
if (!hasLoadMoreView()) {
return
}
if (position < baseQuickAdapter.itemCount - preLoadNumber) {
return
}
if (loadMoreStatus != LoadMoreStatus.Complete) {
return
}
if (loadMoreStatus == LoadMoreStatus.Loading) {
return
}
if (!mNextLoadEnable) {
return
}
invokeLoadMoreListener()
}
/**
* 触发加载更多监听
*/
private fun invokeLoadMoreListener() {
loadMoreStatus = LoadMoreStatus.Loading
baseQuickAdapter.recyclerViewOrNull?.let {
it.post { mLoadMoreListener?.onLoadMore() }
} ?: mLoadMoreListener?.onLoadMore()
}
/**
* check if full page after [BaseQuickAdapter.setNewInstance] [BaseQuickAdapter.setList],
* if full, it will enable load more again.
*
* 用来检查数据是否满一屏,如果满足条件,再开启
*
*/
fun checkDisableLoadMoreIfNotFullPage() {
if (isEnableLoadMoreIfNotFullPage) {
return
}
// 先把标记位设置为false
mNextLoadEnable = false
val recyclerView = baseQuickAdapter.recyclerViewOrNull ?: return
val manager = recyclerView.layoutManager ?: return
if (manager is LinearLayoutManager) {
recyclerView.postDelayed({
if (isFullScreen(manager)) {
mNextLoadEnable = true
}
}, 50)
} else if (manager is StaggeredGridLayoutManager) {
recyclerView.postDelayed({
val positions = IntArray(manager.spanCount)
manager.findLastCompletelyVisibleItemPositions(positions)
val pos = getTheBiggestNumber(positions) + 1
if (pos != baseQuickAdapter.itemCount) {
mNextLoadEnable = true
}
}, 50)
}
}
private fun isFullScreen(llm: LinearLayoutManager): Boolean {
return (llm.findLastCompletelyVisibleItemPosition() + 1) != baseQuickAdapter.itemCount ||
llm.findFirstCompletelyVisibleItemPosition() != 0
}
private fun getTheBiggestNumber(numbers: IntArray?): Int {
var tmp = -1
if (numbers == null || numbers.isEmpty()) {
return tmp
}
for (num in numbers) {
if (num > tmp) {
tmp = num
}
}
return tmp
}
/**
* Refresh end, no more data
*
* @param gone if true gone the load more view
*/
@JvmOverloads
fun loadMoreEnd(gone: Boolean = false) {
if (!hasLoadMoreView()) {
return
}
// mNextLoadEnable = false
isLoadEndMoreGone = gone
loadMoreStatus = LoadMoreStatus.End
if (gone) {
baseQuickAdapter.notifyItemRemoved(loadMoreViewPosition)
} else {
baseQuickAdapter.notifyItemChanged(loadMoreViewPosition)
}
}
/**
* Refresh complete
*/
fun loadMoreComplete() {
if (!hasLoadMoreView()) {
return
}
// mNextLoadEnable = true
loadMoreStatus = LoadMoreStatus.Complete
baseQuickAdapter.notifyItemChanged(loadMoreViewPosition)
checkDisableLoadMoreIfNotFullPage()
}
/**
* Refresh failed
*/
fun loadMoreFail() {
if (!hasLoadMoreView()) {
return
}
loadMoreStatus = LoadMoreStatus.Fail
baseQuickAdapter.notifyItemChanged(loadMoreViewPosition)
}
/**
* 设置加载监听事件
* @param listener OnLoadMoreListener?
*/
override fun setOnLoadMoreListener(listener: OnLoadMoreListener?) {
this.mLoadMoreListener = listener
isEnableLoadMore = true
}
/**
* 重置状态
*/
internal fun reset() {
if (mLoadMoreListener != null) {
isEnableLoadMore = true
loadMoreStatus = LoadMoreStatus.Complete
}
}
}
\ No newline at end of file
package com.chad.library.adapter.base.module
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.listener.OnUpFetchListener
import com.chad.library.adapter.base.listener.UpFetchListenerImp
/**
* @author: limuyang
* @date: 2019-11-29
* @Description: 向上加载
*/
/**
* 需要【向上加载更多】功能的,[BaseQuickAdapter]继承此接口
*/
interface UpFetchModule {
/**
* 重写此方法,返回自定义模块
* @param baseQuickAdapter BaseQuickAdapter<*, *>
* @return BaseUpFetchModule
*/
fun addUpFetchModule(baseQuickAdapter: BaseQuickAdapter<*, *>): BaseUpFetchModule {
return BaseUpFetchModule(baseQuickAdapter)
}
}
open class BaseUpFetchModule(private val baseQuickAdapter: BaseQuickAdapter<*, *>) : UpFetchListenerImp {
private var mUpFetchListener: OnUpFetchListener? = null
var isUpFetchEnable = false
var isUpFetching = false
/**
* start up fetch position, default is 1.
*/
var startUpFetchPosition = 1
internal fun autoUpFetch(position: Int) {
if (!isUpFetchEnable || isUpFetching) {
return
}
if (position <= startUpFetchPosition) {
mUpFetchListener?.onUpFetch()
}
}
override fun setOnUpFetchListener(listener: OnUpFetchListener?) {
this.mUpFetchListener = listener
}
}
package com.chad.library.adapter.base.provider
import android.content.Context
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import com.chad.library.adapter.base.BaseProviderMultiAdapter
import com.chad.library.adapter.base.util.getItemView
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import java.lang.ref.WeakReference
/**
* [BaseProviderMultiAdapter] 的Provider基类
* @param T 数据类型
*/
abstract class BaseItemProvider<T> {
lateinit var context: Context
private var weakAdapter: WeakReference<BaseProviderMultiAdapter<T>>? = null
private val clickViewIds by lazy(LazyThreadSafetyMode.NONE) { ArrayList<Int>() }
private val longClickViewIds by lazy(LazyThreadSafetyMode.NONE) { ArrayList<Int>() }
internal fun setAdapter(adapter: BaseProviderMultiAdapter<T>) {
weakAdapter = WeakReference(adapter)
}
open fun getAdapter(): BaseProviderMultiAdapter<T>? {
return weakAdapter?.get()
}
abstract val itemViewType: Int
abstract val layoutId: Int
@LayoutRes
get
abstract fun convert(helper: BaseViewHolder, item: T)
open fun convert(helper: BaseViewHolder, item: T, payloads: List<Any>) {}
/**
* (可选重写)创建 ViewHolder。
* 默认实现返回[BaseViewHolder],可重写返回自定义 ViewHolder
*
* @param parent
*/
open fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return BaseViewHolder(parent.getItemView(layoutId))
}
/**
* (可选重写)ViewHolder创建完毕以后的回掉方法。
* @param viewHolder VH
*/
open fun onViewHolderCreated(viewHolder: BaseViewHolder, viewType: Int) {}
/**
* Called when a view created by this [BaseItemProvider] has been attached to a window.
* 当此[BaseItemProvider]出现在屏幕上的时候,会调用此方法
*
* This can be used as a reasonable signal that the view is about to be seen
* by the user. If the [BaseItemProvider] previously freed any resources in
* [onViewDetachedFromWindow][.onViewDetachedFromWindow]
* those resources should be restored here.
*
* @param holder Holder of the view being attached
*/
open fun onViewAttachedToWindow(holder: BaseViewHolder) {}
/**
* Called when a view created by this [BaseItemProvider] has been detached from its
* window.
* 当此[BaseItemProvider]从屏幕上移除的时候,会调用此方法
*
* Becoming detached from the window is not necessarily a permanent condition;
* the consumer of an Adapter's views may choose to cache views offscreen while they
* are not visible, attaching and detaching them as appropriate.
*
* @param holder Holder of the view being detached
*/
open fun onViewDetachedFromWindow(holder: BaseViewHolder) {}
/**
* item 若想实现条目点击事件则重写该方法
* @param helper VH
* @param data T
* @param position Int
*/
open fun onClick(helper: BaseViewHolder, view: View, data: T, position: Int) {}
/**
* item 若想实现条目长按事件则重写该方法
* @param helper VH
* @param data T
* @param position Int
* @return Boolean
*/
open fun onLongClick(helper: BaseViewHolder, view: View, data: T, position: Int): Boolean {
return false
}
open fun onChildClick(helper: BaseViewHolder, view: View, data: T, position: Int) {}
open fun onChildLongClick(helper: BaseViewHolder, view: View, data: T, position: Int): Boolean {
return false
}
fun addChildClickViewIds(@IdRes vararg ids: Int) {
ids.forEach {
this.clickViewIds.add(it)
}
}
fun getChildClickViewIds() = this.clickViewIds
fun addChildLongClickViewIds(@IdRes vararg ids: Int) {
ids.forEach {
this.longClickViewIds.add(it)
}
}
fun getChildLongClickViewIds() = this.longClickViewIds
}
\ No newline at end of file
package com.chad.library.adapter.base.provider
import com.chad.library.adapter.base.BaseNodeAdapter
import com.chad.library.adapter.base.entity.node.BaseNode
abstract class BaseNodeProvider : BaseItemProvider<BaseNode>() {
override fun getAdapter(): BaseNodeAdapter? {
return super.getAdapter() as? BaseNodeAdapter
}
}
\ No newline at end of file
package com.chad.library.adapter.base.util
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
/**
* 扩展方法,用于获取View
* @receiver ViewGroup parent
* @param layoutResId Int
* @return View
*/
fun ViewGroup.getItemView(@LayoutRes layoutResId: Int): View {
return LayoutInflater.from(this.context).inflate(layoutResId, this, false)
}
\ No newline at end of file
package com.chad.library.adapter.base.viewholder
import android.view.View
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
/**
* 方便 DataBinding 的使用
*
* @param BD : ViewDataBinding
* @property dataBinding BD?
* @constructor
*/
open class BaseDataBindingHolder<BD : ViewDataBinding>(view: View) : BaseViewHolder(view) {
val dataBinding = DataBindingUtil.bind<BD>(view)
}
\ No newline at end of file
package com.chad.library.adapter.base.viewholder
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.util.SparseArray
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.*
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
/**
* ViewHolder 基类
*/
@Keep
open class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) {
/**
* Views indexed with their IDs
*/
private val views: SparseArray<View> = SparseArray()
/**
* 如果使用了 DataBinding 绑定 View,可调用此方法获取 [ViewDataBinding]
*
* Deprecated, Please use [BaseDataBindingHolder]
*
* @return B?
*/
@Deprecated("Please use BaseDataBindingHolder class", ReplaceWith("DataBindingUtil.getBinding(itemView)", "androidx.databinding.DataBindingUtil"))
open fun <B : ViewDataBinding> getBinding(): B? = DataBindingUtil.getBinding(itemView)
open fun <T : View> getView(@IdRes viewId: Int): T {
val view = getViewOrNull<T>(viewId)
checkNotNull(view) { "No view found with id $viewId" }
return view
}
@Suppress("UNCHECKED_CAST")
open fun <T : View> getViewOrNull(@IdRes viewId: Int): T? {
val view = views.get(viewId)
if (view == null) {
itemView.findViewById<T>(viewId)?.let {
views.put(viewId, it)
return it
}
}
return view as? T
}
open fun <T : View> Int.findView(): T? {
return itemView.findViewById(this)
}
open fun setText(@IdRes viewId: Int, value: CharSequence?): BaseViewHolder {
getView<TextView>(viewId).text = value
return this
}
open fun setText(@IdRes viewId: Int, @StringRes strId: Int): BaseViewHolder? {
getView<TextView>(viewId).setText(strId)
return this
}
open fun setTextColor(@IdRes viewId: Int, @ColorInt color: Int): BaseViewHolder {
getView<TextView>(viewId).setTextColor(color)
return this
}
open fun setTextColorRes(@IdRes viewId: Int, @ColorRes colorRes: Int): BaseViewHolder {
getView<TextView>(viewId).setTextColor(ContextCompat.getColor(itemView.context, colorRes))
return this
}
open fun setImageResource(@IdRes viewId: Int, @DrawableRes imageResId: Int): BaseViewHolder {
getView<ImageView>(viewId).setImageResource(imageResId)
return this
}
open fun setImageDrawable(@IdRes viewId: Int, drawable: Drawable?): BaseViewHolder {
getView<ImageView>(viewId).setImageDrawable(drawable)
return this
}
open fun setImageBitmap(@IdRes viewId: Int, bitmap: Bitmap?): BaseViewHolder {
getView<ImageView>(viewId).setImageBitmap(bitmap)
return this
}
open fun setBackgroundColor(@IdRes viewId: Int, @ColorInt color: Int): BaseViewHolder {
getView<View>(viewId).setBackgroundColor(color)
return this
}
open fun setBackgroundResource(@IdRes viewId: Int, @DrawableRes backgroundRes: Int): BaseViewHolder {
getView<View>(viewId).setBackgroundResource(backgroundRes)
return this
}
open fun setVisible(@IdRes viewId: Int, isVisible: Boolean): BaseViewHolder {
val view = getView<View>(viewId)
view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
return this
}
open fun setGone(@IdRes viewId: Int, isGone: Boolean): BaseViewHolder {
val view = getView<View>(viewId)
view.visibility = if (isGone) View.GONE else View.VISIBLE
return this
}
open fun setEnabled(@IdRes viewId: Int, isEnabled: Boolean): BaseViewHolder {
getView<View>(viewId).isEnabled = isEnabled
return this
}
}
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:drawable="@drawable/brvah_sample_footer_loading"
android:duration="500"
android:fromDegrees="0.0"
android:pivotX="50.0%"
android:pivotY="50.0%"
android:toDegrees="360.0" />
</item>
</layer-list>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40">
<LinearLayout
android:id="@+id/load_more_loading_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<ProgressBar
android:id="@+id/loading_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:layout_marginRight="@dimen/dp_4"/>
<TextView
android:id="@+id/loading_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_4"
android:text="@string/brvah_loading"
android:textColor="@android:color/black"
android:textSize="@dimen/sp_14"/>
</LinearLayout>
<FrameLayout
android:id="@+id/load_more_load_fail_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:id="@+id/tv_prompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/brvah_load_failed"/>
</FrameLayout>
<FrameLayout
android:id="@+id/load_more_load_complete_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/brvah_load_complete"
android:textColor="@android:color/darker_gray"/>
</FrameLayout>
<FrameLayout
android:id="@+id/load_more_load_end_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/brvah_load_end"
android:textColor="@android:color/darker_gray"/>
</FrameLayout>
</FrameLayout>
\ No newline at end of file
<resources>
<string name="brvah_loading">Loading...</string>
<string name="brvah_load_failed">load more failed</string>
<string name="brvah_load_end">No more data</string>
<string name="brvah_load_complete">Click to load more</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="brvah_load_failed">加載失敗,請點我重試</string>
<string name="brvah_load_end">沒有更多數據</string>
<string name="brvah_load_complete">點擊加載更多</string>
<string name="brvah_loading">正在加載中...</string>
</resources>
\ No newline at end of file
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="def_height">50dp</dimen>
<dimen name="dp_4">4dp</dimen>
<dimen name="dp_10">10dp</dimen>
<dimen name="dp_40">40dp</dimen>
<dimen name="sp_14">14sp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="BaseQuickAdapter_viewholder_support" type="id"/>
<item name="BaseQuickAdapter_swiping_support" type="id"/>
<item name="BaseQuickAdapter_dragging_support" type="id"/>
<item name="BaseQuickAdapter_databinding_support" type="id"/>
</resources>
\ No newline at end of file
<resources>
<string name="brvah_loading">正在加载中...</string>
<string name="brvah_load_failed">加载失败,请点我重试</string>
<string name="brvah_load_end">没有更多数据</string>
<string name="brvah_load_complete">点击加载更多</string>
</resources>
include ':app', ':library_recyclerview'
rootProject.name='hooloo'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment