This commit is contained in:
2025-11-18 15:25:59 +08:00
parent 790f719050
commit 69f2eaf336
1227 changed files with 89317 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+22
View File
@@ -0,0 +1,22 @@
apply plugin: 'com.android.library'
android {
namespace 'chihane.jdaddressselector'
compileSdkVersion 29
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.0'
}
+17
View File
@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\Administrator\AppData\Local\Android\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 *;
#}
+10
View File
@@ -0,0 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chihane.jdaddressselector">
<application
android:allowBackup="true"
android:supportsRtl="true">
</application>
</manifest>
@@ -0,0 +1,56 @@
package chihane.jdaddressselector;
import android.app.Dialog;
import android.content.Context;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
public class BottomDialog extends Dialog {
private Selector selector;
public BottomDialog(Context context) {
super(context, R.style.bottom_dialog);
}
public BottomDialog(Context context, int themeResId) {
super(context, themeResId);
}
public BottomDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
public void init(Context context,Selector selector) {
this.selector = selector;
setContentView(selector.getView());
Window window = getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = dip2px(context, 256);
window.setAttributes(params);
window.setGravity(Gravity.BOTTOM);
}
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public void setOnAddressSelectedListener(SelectedListener listener) {
this.selector.setSelectedListener(listener);
}
public static BottomDialog show(Context context) {
return show(context, null);
}
public static BottomDialog show(Context context, SelectedListener listener) {
BottomDialog dialog = new BottomDialog(context, R.style.bottom_dialog);
dialog.selector.setSelectedListener(listener);
dialog.show();
return dialog;
}
}
@@ -0,0 +1,16 @@
package chihane.jdaddressselector;
import java.util.List;
/**
* Created by dun on 17/2/9.
*/
public interface DataProvider {
void provideData(int currentDeep,int preId,DataReceiver receiver);
interface DataReceiver {
void send(List<ISelectAble> data);
}
}
@@ -0,0 +1,25 @@
package chihane.jdaddressselector;
/**
* Created by dun on 17/2/9.
*/
public interface ISelectAble {
/**
* 显示在栏目上的名字
* */
public String getName();
/**
* 用户设定的id,根据这个id,可以获取级栏目或者指定为最终栏目的id
* */
public int getId();
public String getUuid();
/**
* 自定义类型对象。
* */
public String county_name();
public String city_name();
public String prov_name();;
}
@@ -0,0 +1,71 @@
package chihane.jdaddressselector;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by dun on 17/2/9.
*/
public class SelectAdapter extends BaseAdapter{
List<ISelectAble> datas;
int selectedIndex = Selector.INDEX_INVALID;
public SelectAdapter(List<ISelectAble> datas) {
this.datas = datas;
}
public void setSelectedIndex(int selectedIndex) {
this.selectedIndex = selectedIndex;
}
@Override
public int getCount() {
return datas.size();
}
@Override
public Object getItem(int position) {
return datas.get(position);
}
@Override
public long getItemId(int position) {
return datas.get(position).getId();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_area, parent, false);
holder = new Holder();
holder.textView = (TextView) convertView.findViewById(R.id.textView);
holder.imageViewCheckMark = (ImageView) convertView.findViewById(R.id.imageViewCheckMark);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
ISelectAble item = (ISelectAble) getItem(position);
holder.textView.setText(item.getName());
boolean checked = selectedIndex != Selector.INDEX_INVALID && datas.get(selectedIndex).getId() == item.getId();
holder.textView.setEnabled(!checked);
holder.imageViewCheckMark.setVisibility(checked ? View.VISIBLE : View.GONE);
return convertView;
}
class Holder {
TextView textView;
ImageView imageViewCheckMark;
}
}
@@ -0,0 +1,11 @@
package chihane.jdaddressselector;
import java.util.ArrayList;
public interface SelectedListener {
/**
* 回调接口,根据选择深度,按顺序返回选择的对象。
* */
void onAddressSelected(ArrayList<ISelectAble> selectAbles);
}
@@ -0,0 +1,259 @@
package chihane.jdaddressselector;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import java.util.ArrayList;
import java.util.List;
public class Selector implements AdapterView.OnItemClickListener {
public static final int INDEX_INVALID = -1;
private final Context context;
private SelectedListener listener;
private View view;
private View indicator;
private LinearLayout ll_tabLayout;
private ProgressBar progressBar;
private ListView listView;
private int tabIndex = 0;
/* 每个tab的adapter */ List<List<ISelectAble>> allDatas = new ArrayList<>();
private SelectAdapter[] adapters;
/*选择的深度*/
private int selectDeep;
private int[] selectedIndex;
DataProvider dataProvider;
public void setDataProvider(DataProvider dataProvider) {
this.dataProvider = dataProvider;
getNextData(0);
}
public Selector(Context context, int deep) {
this.context = context;
this.allDatas = new ArrayList<>(deep);
selectedIndex = new int[deep];
this.selectDeep = deep;
for (int i = 0; i < deep; i++) {
allDatas.add(new ArrayList<ISelectAble>());
}
initAdapters();
initViews();
}
private void initAdapters() {
adapters = new SelectAdapter[allDatas.size()];
for (int i = 0; i < selectDeep; i++) {
adapters[i] = new SelectAdapter(allDatas.get(i));
}
}
private TextView[] tabs;
private void initViews() {
view = LayoutInflater.from(context).inflate(R.layout.address_selector, null);
this.progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
this.listView = (ListView) view.findViewById(R.id.listView);
this.indicator = view.findViewById(R.id.indicator);
this.ll_tabLayout = (LinearLayout) view.findViewById(R.id.layout_tab);
tabs = new TextView[allDatas.size()];
for (int i = 0; i < allDatas.size(); i++) {
//动态新增TextView
TextView textView = (TextView) LayoutInflater.from(context).inflate(R.layout.simple_text_view, ll_tabLayout, false);
ll_tabLayout.addView(textView);
//绑定TextView的点击事件
final int finalI = i;
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//设置tab 下标
tabIndex = finalI + 1;
//更新adapter
listView.setAdapter(adapters[finalI]);
//设置选择位置
if (selectedIndex[finalI] != INDEX_INVALID) {
listView.setSelection(selectedIndex[finalI]);
}
updateTabsVisibility(tabIndex-1);
updateIndicator(tabIndex - 1);
}
});
tabs[i] = textView;
}
this.listView.setOnItemClickListener(this);
updateIndicator(tabIndex);
}
public View getView() {
return view;
}
/**
* 指示器动画
*/
private void updateIndicator(final int tabIndex) {
view.post(new Runnable() {
@Override
public void run() {
buildIndicatorAnimatorTowards(tabs[tabIndex]).start();
}
});
}
private AnimatorSet buildIndicatorAnimatorTowards(TextView tab) {
ObjectAnimator xAnimator = ObjectAnimator.ofFloat(indicator, "X", indicator.getX(), tab.getX());
final ViewGroup.LayoutParams params = indicator.getLayoutParams();
ValueAnimator widthAnimator = ValueAnimator.ofInt(params.width, tab.getMeasuredWidth());
widthAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
params.width = (int) animation.getAnimatedValue();
indicator.setLayoutParams(params);
}
});
AnimatorSet set = new AnimatorSet();
set.setInterpolator(new FastOutSlowInInterpolator());
set.playTogether(xAnimator, widthAnimator);
return set;
}
private void updateTabsVisibility(int index) {
for (int i = 0; i < tabs.length; i++) {
TextView tv = tabs[i];
tv.setVisibility(allDatas.get(i).size() != 0 ? View.VISIBLE : View.GONE);
tv.setEnabled(index != i);
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
this.selectedIndex[tabIndex - 1] = position;
ISelectAble selectAble = allDatas.get(tabIndex - 1).get(position);
tabs[tabIndex-1].setText(selectAble.getName());
for (int i = tabIndex; i < this.allDatas.size(); i++) {
tabs[i].setText("请选择");
allDatas.get(i).clear();
adapters[i].setSelectedIndex(-1);
adapters[i].notifyDataSetChanged();
this.selectedIndex[i] = INDEX_INVALID;
}
this.adapters[tabIndex - 1].setSelectedIndex(position);
this.adapters[tabIndex - 1].notifyDataSetChanged();
// if(tabIndex == selectDeep-1)
// {
// callbackInternalno();
// }
if (tabIndex == selectDeep) {
callbackInternal();
return;
}
updateTabsVisibility(tabIndex -1);
updateIndicator(tabIndex);
getNextData(selectAble.getId());
}
/**
* 根据当前集合选择的id,向用户获取下一级子集的数据
*/
private void getNextData(int preId) {
if (dataProvider == null) {
return;
}
progressBar.setVisibility(View.VISIBLE);
dataProvider.provideData(tabIndex, preId, new DataProvider.DataReceiver() {
@Override
public void send(List<ISelectAble> data) {
if (data.size() > 0) {
//更新当前tab下标
allDatas.get(tabIndex).clear();
allDatas.get(tabIndex).addAll(data);
adapters[tabIndex].notifyDataSetChanged();
listView.setAdapter(adapters[tabIndex]);
} else {
//次级没有内容,直接回调
callbackInternal();
}
updateTabsVisibility(tabIndex);
updateProgressVisibility();
updateIndicator(tabIndex);
tabIndex = tabIndex + 1 >= selectDeep ? selectDeep : tabIndex + 1;
}
});
}
private void callbackInternal() {
if (listener != null) {
ArrayList<ISelectAble> result = new ArrayList<>(allDatas.size());
for (int i = 0; i < selectDeep; i++) {
ISelectAble resultBean = allDatas.get(i) == null
|| selectedIndex[i] == INDEX_INVALID ? null : allDatas.get(i).get(selectedIndex[i]);
result.add(resultBean);
}
listener.onAddressSelected(result);
}
}
private void callbackInternalno() {
if (listener != null) {
ArrayList<ISelectAble> result = new ArrayList<>(allDatas.size()-1);
for (int i = 0; i < selectDeep-1; i++) {
ISelectAble resultBean = allDatas.get(i) == null
|| selectedIndex[i] == INDEX_INVALID ? null : allDatas.get(i).get(selectedIndex[i]);
result.add(resultBean);
}
listener.onAddressSelected(result);
}
}
private void updateProgressVisibility() {
ListAdapter adapter = listView.getAdapter();
int itemCount = adapter.getCount();
progressBar.setVisibility(itemCount > 0 ? View.GONE : View.VISIBLE);
}
public SelectedListener getOnAddressSelectedListener() {
return listener;
}
public void setSelectedListener(SelectedListener listener) {
this.listener = listener;
}
}
@@ -0,0 +1,26 @@
package chihane.jdaddressselector.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ListView;
public class UninterceptableListView extends ListView {
public UninterceptableListView(Context context) {
super(context);
}
public UninterceptableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public UninterceptableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.onTouchEvent(ev);
}
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="225"
android:fromXDelta="0%"
android:fromYDelta="100%"
android:interpolator="@android:anim/decelerate_interpolator"
android:toXDelta="0%"
android:toYDelta="0%" />
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="195"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:interpolator="@android:anim/accelerate_interpolator"
android:toXDelta="0%"
android:toYDelta="100%" />
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/tab_text_sel" android:state_enabled="false" />
<item android:color="@android:color/black" android:state_enabled="true" />
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

@@ -0,0 +1,62 @@
<?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="match_parent"
android:background="@android:color/white"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<HorizontalScrollView
android:scrollbars="none"
android:id="@+id/layout_hs_tab"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/layout_tab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
<View
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="2dp"
android:layout_below="@+id/layout_hs_tab"
android:background="@color/tab_text_sel" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e8e8e8" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<chihane.jdaddressselector.widget.UninterceptableListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@null" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
</LinearLayout>
@@ -0,0 +1,30 @@
<?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"
android:padding="10dp">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/selector_text_color_tab"
android:textSize="14sp" />
<ImageView
android:id="@+id/imageViewCheckMark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_toEndOf="@+id/textView"
android:layout_toRightOf="@+id/textView"
android:contentDescription="@string/desc_check_mark"
android:src="@drawable/chose_card"
android:visibility="gone" />
</RelativeLayout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/tab"
>
</TextView>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="tab_text_sel">#8D2316</color>
</resources>
+3
View File
@@ -0,0 +1,3 @@
<resources>
<string name="desc_check_mark">check mark</string>
</resources>
+29
View File
@@ -0,0 +1,29 @@
<resources>
<style name="tab">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:ellipsize">end</item>
<item name="android:maxLines">1</item>
<item name="android:padding">10dp</item>
<item name="android:text">请选择</item>
<item name="android:textColor">@color/selector_text_color_tab</item>
<item name="android:textSize">14sp</item>
<item name="android:visibility">gone</item>
</style>
<style name="bottom_dialog" parent="android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="Animation_Bottom_Dialog">
<item name="android:windowEnterAnimation">@anim/bottom_dialog_enter</item>
<item name="android:windowExitAnimation">@anim/bottom_dialog_exit</item>
</style>
</resources>