chore: merge peizhen as regular directory (replace submodule)
This commit is contained in:
1
peizhen/citypicker/.gitignore
vendored
Normal file
1
peizhen/citypicker/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
41
peizhen/citypicker/build.gradle
Normal file
41
peizhen/citypicker/build.gradle
Normal file
@@ -0,0 +1,41 @@
|
||||
apply plugin: 'com.android.library'
|
||||
// apply plugin: 'com.github.dcendents.android-maven' // 与 Gradle 8.9 不兼容,暂时注释
|
||||
group='com.github.zaaach'
|
||||
|
||||
android {
|
||||
namespace 'com.zaaach.citypicker'
|
||||
compileSdk 34
|
||||
compileSdkVersion 34
|
||||
buildToolsVersion "30.0.2"
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 27
|
||||
versionCode 7
|
||||
versionName "2.0.3"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
testImplementation 'junit:junit:4.12'
|
||||
compileOnly 'androidx.appcompat:appcompat:1.0.0'
|
||||
compileOnly 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
api 'com.google.android.material:material:1.0.0'
|
||||
}
|
||||
17
peizhen/citypicker/proguard-rules.pro
vendored
Normal file
17
peizhen/citypicker/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in F:\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 *;
|
||||
#}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.zaaach.citypicker;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumentation test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
assertEquals("com.zaaach.citypicker.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
11
peizhen/citypicker/src/main/AndroidManifest.xml
Normal file
11
peizhen/citypicker/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.zaaach.citypicker">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true">
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
peizhen/citypicker/src/main/assets/china_cities_v2.db
Normal file
BIN
peizhen/citypicker/src/main/assets/china_cities_v2.db
Normal file
Binary file not shown.
@@ -0,0 +1,132 @@
|
||||
package com.zaaach.citypicker;
|
||||
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.zaaach.citypicker.adapter.OnPickListener;
|
||||
import com.zaaach.citypicker.model.HotCity;
|
||||
import com.zaaach.citypicker.model.LocateState;
|
||||
import com.zaaach.citypicker.model.LocatedCity;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: Bro0cL
|
||||
* @Date: 2018/2/6 17:52
|
||||
*/
|
||||
public class CityPicker {
|
||||
private static final String TAG = "CityPicker";
|
||||
|
||||
private WeakReference<FragmentActivity> mContext;
|
||||
private WeakReference<Fragment> mFragment;
|
||||
private WeakReference<FragmentManager> mFragmentManager;
|
||||
|
||||
private boolean enableAnim;
|
||||
private int mAnimStyle;
|
||||
private LocatedCity mLocation;
|
||||
private List<HotCity> mHotCities;
|
||||
private OnPickListener mOnPickListener;
|
||||
|
||||
private CityPicker(){}
|
||||
|
||||
private CityPicker(Fragment fragment){
|
||||
this(fragment.getActivity(), fragment);
|
||||
mFragmentManager = new WeakReference<>(fragment.getChildFragmentManager());
|
||||
}
|
||||
|
||||
private CityPicker(FragmentActivity activity){
|
||||
this(activity, null);
|
||||
mFragmentManager = new WeakReference<>(activity.getSupportFragmentManager());
|
||||
}
|
||||
|
||||
private CityPicker(FragmentActivity activity, Fragment fragment){
|
||||
mContext = new WeakReference<>(activity);
|
||||
mFragment = new WeakReference<>(fragment);
|
||||
}
|
||||
|
||||
public static CityPicker from(Fragment fragment){
|
||||
return new CityPicker(fragment);
|
||||
}
|
||||
|
||||
public static CityPicker from(FragmentActivity activity){
|
||||
return new CityPicker(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置动画效果
|
||||
* @param animStyle
|
||||
* @return
|
||||
*/
|
||||
public CityPicker setAnimationStyle(@StyleRes int animStyle) {
|
||||
this.mAnimStyle = animStyle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前已经定位的城市
|
||||
* @param location
|
||||
* @return
|
||||
*/
|
||||
public CityPicker setLocatedCity(LocatedCity location) {
|
||||
this.mLocation = location;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CityPicker setHotCities(List<HotCity> data){
|
||||
this.mHotCities = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用动画效果,默认为false
|
||||
* @param enable
|
||||
* @return
|
||||
*/
|
||||
public CityPicker enableAnimation(boolean enable){
|
||||
this.enableAnim = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置选择结果的监听器
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public CityPicker setOnPickListener(OnPickListener listener){
|
||||
this.mOnPickListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void show(){
|
||||
FragmentTransaction ft = mFragmentManager.get().beginTransaction();
|
||||
final Fragment prev = mFragmentManager.get().findFragmentByTag(TAG);
|
||||
if (prev != null){
|
||||
ft.remove(prev).commit();
|
||||
ft = mFragmentManager.get().beginTransaction();
|
||||
}
|
||||
ft.addToBackStack(null);
|
||||
final CityPickerDialogFragment cityPickerFragment =
|
||||
CityPickerDialogFragment.newInstance(enableAnim);
|
||||
cityPickerFragment.setLocatedCity(mLocation);
|
||||
cityPickerFragment.setHotCities(mHotCities);
|
||||
cityPickerFragment.setAnimationStyle(mAnimStyle);
|
||||
cityPickerFragment.setOnPickListener(mOnPickListener);
|
||||
cityPickerFragment.show(ft, TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 定位完成
|
||||
* @param location
|
||||
* @param state
|
||||
*/
|
||||
public void locateComplete(LocatedCity location, @LocateState.State int state){
|
||||
CityPickerDialogFragment fragment = (CityPickerDialogFragment) mFragmentManager.get().findFragmentByTag(TAG);
|
||||
if (fragment != null){
|
||||
fragment.locationChanged(location, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
package com.zaaach.citypicker;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zaaach.citypicker.adapter.CityListAdapter;
|
||||
import com.zaaach.citypicker.adapter.InnerListener;
|
||||
import com.zaaach.citypicker.adapter.OnPickListener;
|
||||
import com.zaaach.citypicker.adapter.decoration.DividerItemDecoration;
|
||||
import com.zaaach.citypicker.adapter.decoration.SectionItemDecoration;
|
||||
import com.zaaach.citypicker.db.DBManager;
|
||||
import com.zaaach.citypicker.model.City;
|
||||
import com.zaaach.citypicker.model.HotCity;
|
||||
import com.zaaach.citypicker.model.LocateState;
|
||||
import com.zaaach.citypicker.model.LocatedCity;
|
||||
import com.zaaach.citypicker.util.ScreenUtil;
|
||||
import com.zaaach.citypicker.view.SideIndexBar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: Bro0cL
|
||||
* @Date: 2018/2/6 20:50
|
||||
*/
|
||||
public class CityPickerDialogFragment extends DialogFragment implements TextWatcher,
|
||||
View.OnClickListener, SideIndexBar.OnIndexTouchedChangedListener, InnerListener {
|
||||
private View mContentView;
|
||||
private RecyclerView mRecyclerView;
|
||||
private View mEmptyView;
|
||||
private TextView mOverlayTextView;
|
||||
private SideIndexBar mIndexBar;
|
||||
private EditText mSearchBox;
|
||||
private TextView mCancelBtn;
|
||||
private ImageView mClearAllBtn;
|
||||
|
||||
private LinearLayoutManager mLayoutManager;
|
||||
private CityListAdapter mAdapter;
|
||||
private List<City> mAllCities;
|
||||
private List<HotCity> mHotCities;
|
||||
private List<City> mResults;
|
||||
|
||||
private DBManager dbManager;
|
||||
|
||||
private int height;
|
||||
private int width;
|
||||
|
||||
private boolean enableAnim = false;
|
||||
private int mAnimStyle = R.style.DefaultCityPickerAnimation;
|
||||
private LocatedCity mLocatedCity;
|
||||
private int locateState;
|
||||
private OnPickListener mOnPickListener;
|
||||
|
||||
/**
|
||||
* 获取实例
|
||||
* @param enable 是否启用动画效果
|
||||
* @return
|
||||
*/
|
||||
public static CityPickerDialogFragment newInstance(boolean enable){
|
||||
final CityPickerDialogFragment fragment = new CityPickerDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean("cp_enable_anim", enable);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(STYLE_NORMAL, R.style.CityPickerStyle);
|
||||
}
|
||||
|
||||
public void setLocatedCity(LocatedCity location){
|
||||
mLocatedCity = location;
|
||||
}
|
||||
|
||||
public void setHotCities(List<HotCity> data){
|
||||
if (data != null && !data.isEmpty()){
|
||||
this.mHotCities = data;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
public void setAnimationStyle(@StyleRes int resId){
|
||||
this.mAnimStyle = resId <= 0 ? mAnimStyle : resId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
mContentView = inflater.inflate(R.layout.cp_dialog_city_picker, container, false);
|
||||
return mContentView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
initData();
|
||||
initViews();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
RelativeLayout rl_back = mContentView.findViewById(R.id.rl_back);
|
||||
rl_back.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
dismiss();
|
||||
if (mOnPickListener != null){
|
||||
mOnPickListener.onCancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
TextView tv_title = mContentView.findViewById(R.id.tv_title);
|
||||
mRecyclerView = mContentView.findViewById(R.id.cp_city_recyclerview);
|
||||
mLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
|
||||
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||
mRecyclerView.setHasFixedSize(true);
|
||||
mRecyclerView.addItemDecoration(new SectionItemDecoration(getActivity(), mAllCities), 0);
|
||||
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity()), 1);
|
||||
mAdapter = new CityListAdapter(getActivity(), mAllCities, mHotCities, locateState);
|
||||
mAdapter.autoLocate(true);
|
||||
mAdapter.setInnerListener(this);
|
||||
mAdapter.setLayoutManager(mLayoutManager);
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
//确保定位城市能正常刷新
|
||||
if (newState == RecyclerView.SCROLL_STATE_IDLE){
|
||||
mAdapter.refreshLocationItem();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
}
|
||||
});
|
||||
|
||||
mEmptyView = mContentView.findViewById(R.id.cp_empty_view);
|
||||
mOverlayTextView = mContentView.findViewById(R.id.cp_overlay);
|
||||
|
||||
mIndexBar = mContentView.findViewById(R.id.cp_side_index_bar);
|
||||
mIndexBar.setNavigationBarHeight(ScreenUtil.getNavigationBarHeight(getActivity()));
|
||||
mIndexBar.setOverlayTextView(mOverlayTextView)
|
||||
.setOnIndexChangedListener(this);
|
||||
|
||||
mSearchBox = mContentView.findViewById(R.id.cp_search_box);
|
||||
mSearchBox.addTextChangedListener(this);
|
||||
|
||||
// mCancelBtn = mContentView.findViewById(R.id.cp_cancel);
|
||||
// mClearAllBtn = mContentView.findViewById(R.id.cp_clear_all);
|
||||
// mCancelBtn.setOnClickListener(this);
|
||||
// mClearAllBtn.setOnClickListener(this);
|
||||
}
|
||||
|
||||
private void initData() {
|
||||
Bundle args = getArguments();
|
||||
if (args != null) {
|
||||
enableAnim = args.getBoolean("cp_enable_anim");
|
||||
}
|
||||
//初始化热门城市
|
||||
if (mHotCities == null || mHotCities.isEmpty()) {
|
||||
mHotCities = new ArrayList<>();
|
||||
mHotCities.add(new HotCity("北京", "北京", "101010100"));
|
||||
mHotCities.add(new HotCity("上海", "上海", "101020100"));
|
||||
mHotCities.add(new HotCity("广州", "广东", "101280101"));
|
||||
mHotCities.add(new HotCity("深圳", "广东", "101280601"));
|
||||
mHotCities.add(new HotCity("天津", "天津", "101030100"));
|
||||
mHotCities.add(new HotCity("杭州", "浙江", "101210101"));
|
||||
mHotCities.add(new HotCity("南京", "江苏", "101190101"));
|
||||
mHotCities.add(new HotCity("成都", "四川", "101270101"));
|
||||
mHotCities.add(new HotCity("武汉", "湖北", "101200101"));
|
||||
}
|
||||
//初始化定位城市,默认为空时会自动回调定位
|
||||
if (mLocatedCity == null){
|
||||
mLocatedCity = new LocatedCity(getString(R.string.cp_locating), "未知", "0");
|
||||
locateState = LocateState.LOCATING;
|
||||
}else{
|
||||
locateState = LocateState.SUCCESS;
|
||||
}
|
||||
|
||||
dbManager = new DBManager(getActivity());
|
||||
mAllCities = dbManager.getAllCities();
|
||||
mAllCities.add(0, mLocatedCity);
|
||||
mAllCities.add(1, new HotCity("热门城市", "未知", "0"));
|
||||
mResults = mAllCities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
Dialog dialog = getDialog();
|
||||
dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK){
|
||||
if (mOnPickListener != null){
|
||||
mOnPickListener.onCancel();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
measure();
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
|
||||
window.setGravity(Gravity.BOTTOM);
|
||||
window.setLayout(width, height - ScreenUtil.getStatusBarHeight(getActivity()));
|
||||
if (enableAnim) {
|
||||
window.setWindowAnimations(mAnimStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//测量宽高
|
||||
private void measure() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
|
||||
DisplayMetrics dm = new DisplayMetrics();
|
||||
getActivity().getWindowManager().getDefaultDisplay().getRealMetrics(dm);
|
||||
height = dm.heightPixels;
|
||||
width = dm.widthPixels;
|
||||
}else{
|
||||
DisplayMetrics dm = getResources().getDisplayMetrics();
|
||||
height = dm.heightPixels;
|
||||
width = dm.widthPixels;
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索框监听 */
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
String keyword = s.toString();
|
||||
if (TextUtils.isEmpty(keyword)){
|
||||
// mClearAllBtn.setVisibility(View.GONE);
|
||||
mEmptyView.setVisibility(View.GONE);
|
||||
mResults = mAllCities;
|
||||
((SectionItemDecoration)(mRecyclerView.getItemDecorationAt(0))).setData(mResults);
|
||||
mAdapter.updateData(mResults);
|
||||
}else {
|
||||
// mClearAllBtn.setVisibility(View.VISIBLE);
|
||||
//开始数据库查找
|
||||
mResults = dbManager.searchCity(keyword);
|
||||
((SectionItemDecoration)(mRecyclerView.getItemDecorationAt(0))).setData(mResults);
|
||||
if (mResults == null || mResults.isEmpty()){
|
||||
mEmptyView.setVisibility(View.VISIBLE);
|
||||
}else {
|
||||
mEmptyView.setVisibility(View.GONE);
|
||||
mAdapter.updateData(mResults);
|
||||
}
|
||||
}
|
||||
mRecyclerView.scrollToPosition(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int id = v.getId();
|
||||
// if (id == R.id.cp_cancel) {
|
||||
// //dismiss();
|
||||
// if (mOnPickListener != null){
|
||||
// mOnPickListener.onCancel();
|
||||
// }
|
||||
// }else if(id == R.id.cp_clear_all){
|
||||
// mSearchBox.setText("");
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onIndexChanged(String index, int position) {
|
||||
//滚动RecyclerView到索引位置
|
||||
mAdapter.scrollToSection(index);
|
||||
}
|
||||
|
||||
public void locationChanged(LocatedCity location, int state){
|
||||
mAdapter.updateLocateState(location, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss(int position, City data) {
|
||||
dismiss();
|
||||
if (mOnPickListener != null){
|
||||
mOnPickListener.onPick(position, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void locate(){
|
||||
if (mOnPickListener != null){
|
||||
mOnPickListener.onLocate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnPickListener(OnPickListener listener){
|
||||
this.mOnPickListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package com.zaaach.citypicker.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zaaach.citypicker.R;
|
||||
import com.zaaach.citypicker.adapter.decoration.GridItemDecoration;
|
||||
import com.zaaach.citypicker.model.City;
|
||||
import com.zaaach.citypicker.model.HotCity;
|
||||
import com.zaaach.citypicker.model.LocateState;
|
||||
import com.zaaach.citypicker.model.LocatedCity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: Bro0cL
|
||||
* @Date: 2018/2/5 12:06
|
||||
*/
|
||||
public class CityListAdapter extends RecyclerView.Adapter<CityListAdapter.BaseViewHolder> {
|
||||
private static final int VIEW_TYPE_LOCATION = 10;
|
||||
private static final int VIEW_TYPE_HOT = 11;
|
||||
|
||||
private Context mContext;
|
||||
private List<City> mData;
|
||||
private List<HotCity> mHotData;
|
||||
private int locateState;
|
||||
private InnerListener mInnerListener;
|
||||
private LinearLayoutManager mLayoutManager;
|
||||
private boolean stateChanged;
|
||||
private boolean autoLocate;
|
||||
|
||||
public CityListAdapter(Context context, List<City> data, List<HotCity> hotData, int state) {
|
||||
this.mData = data;
|
||||
this.mContext = context;
|
||||
this.mHotData = hotData;
|
||||
this.locateState = state;
|
||||
}
|
||||
|
||||
public void autoLocate(boolean auto){
|
||||
autoLocate = auto;
|
||||
}
|
||||
|
||||
public void setLayoutManager(LinearLayoutManager manager){
|
||||
this.mLayoutManager = manager;
|
||||
}
|
||||
|
||||
public void updateData(List<City> data){
|
||||
this.mData = data;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void updateLocateState(LocatedCity location, int state){
|
||||
mData.remove(0);
|
||||
mData.add(0, location);
|
||||
stateChanged = !(locateState == state);
|
||||
locateState = state;
|
||||
refreshLocationItem();
|
||||
}
|
||||
|
||||
public void refreshLocationItem(){
|
||||
//如果定位城市的item可见则进行刷新
|
||||
if (stateChanged && mLayoutManager.findFirstVisibleItemPosition() == 0) {
|
||||
stateChanged = false;
|
||||
notifyItemChanged(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动RecyclerView到索引位置
|
||||
* @param index
|
||||
*/
|
||||
public void scrollToSection(String index){
|
||||
if (mData == null || mData.isEmpty()) return;
|
||||
if (index == null || TextUtils.isEmpty(index) || index.length() < 1) return;
|
||||
String indexFirstChar = index.substring(0, 1);
|
||||
int size = mData.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
City city = mData.get(i);
|
||||
if (city == null || city.getSection() == null) continue;
|
||||
String section = city.getSection();
|
||||
if (section == null || section.length() < 1) continue;
|
||||
String sectionFirstChar = section.substring(0, 1);
|
||||
if (TextUtils.equals(indexFirstChar, sectionFirstChar)){
|
||||
if (mLayoutManager != null){
|
||||
mLayoutManager.scrollToPositionWithOffset(i, 0);
|
||||
// 延迟刷新逻辑已移除以避免 D8 编译问题
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view;
|
||||
switch (viewType){
|
||||
case VIEW_TYPE_LOCATION:
|
||||
view = LayoutInflater.from(mContext).inflate(R.layout.cp_list_item_location_layout, parent, false);
|
||||
return new LocationViewHolder(view);
|
||||
case VIEW_TYPE_HOT:
|
||||
view = LayoutInflater.from(mContext).inflate(R.layout.cp_list_item_hot_layout, parent, false);
|
||||
return new HotViewHolder(view);
|
||||
default:
|
||||
view = LayoutInflater.from(mContext).inflate(R.layout.cp_list_item_default_layout, parent, false);
|
||||
return new DefaultViewHolder(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
|
||||
if (holder instanceof DefaultViewHolder){
|
||||
final int pos = holder.getAdapterPosition();
|
||||
if (pos < 0 || pos >= mData.size()) return;
|
||||
final City data = mData.get(pos);
|
||||
if (data == null) return;
|
||||
String name = data.getName();
|
||||
if (name == null) name = "";
|
||||
((DefaultViewHolder)holder).name.setText(name);
|
||||
((DefaultViewHolder) holder).name.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mInnerListener != null && data != null){
|
||||
mInnerListener.dismiss(pos, data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
//定位城市
|
||||
if (holder instanceof LocationViewHolder){
|
||||
final int pos = holder.getAdapterPosition();
|
||||
if (pos < 0 || pos >= mData.size()) return;
|
||||
final City data = mData.get(pos);
|
||||
if (data == null) return;
|
||||
//设置宽高
|
||||
DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
|
||||
int screenWidth = dm.widthPixels;
|
||||
TypedValue typedValue = new TypedValue();
|
||||
mContext.getTheme().resolveAttribute(R.attr.cpGridItemSpace, typedValue, true);
|
||||
int space = mContext.getResources().getDimensionPixelSize(R.dimen.cp_grid_item_space);
|
||||
int padding = mContext.getResources().getDimensionPixelSize(R.dimen.cp_default_padding);
|
||||
int indexBarWidth = mContext.getResources().getDimensionPixelSize(R.dimen.cp_index_bar_width);
|
||||
int itemWidth = (screenWidth - padding - space * (GridListAdapter.SPAN_COUNT - 1) - indexBarWidth) / GridListAdapter.SPAN_COUNT;
|
||||
ViewGroup.LayoutParams lp = ((LocationViewHolder) holder).container.getLayoutParams();
|
||||
lp.width = itemWidth;
|
||||
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
((LocationViewHolder) holder).container.setLayoutParams(lp);
|
||||
|
||||
switch (locateState){
|
||||
case LocateState.LOCATING:
|
||||
((LocationViewHolder) holder).current.setText(R.string.cp_locating);
|
||||
break;
|
||||
case LocateState.SUCCESS:
|
||||
String cityName = data.getName();
|
||||
if (cityName == null) cityName = "";
|
||||
((LocationViewHolder) holder).current.setText(cityName);
|
||||
break;
|
||||
case LocateState.FAILURE:
|
||||
((LocationViewHolder) holder).current.setText(R.string.cp_locate_failed);
|
||||
break;
|
||||
}
|
||||
((LocationViewHolder) holder).container.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (locateState == LocateState.SUCCESS) {
|
||||
if (mInnerListener != null && data != null) {
|
||||
mInnerListener.dismiss(pos, data);
|
||||
}
|
||||
} else if (locateState == LocateState.FAILURE){
|
||||
locateState = LocateState.LOCATING;
|
||||
notifyItemChanged(0);
|
||||
if (mInnerListener != null){
|
||||
mInnerListener.locate();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
//第一次弹窗,如果未定位则自动定位
|
||||
if (autoLocate && locateState == LocateState.LOCATING && mInnerListener != null){
|
||||
mInnerListener.locate();
|
||||
autoLocate = false;
|
||||
}
|
||||
}
|
||||
//热门城市
|
||||
if (holder instanceof HotViewHolder){
|
||||
final int pos = holder.getAdapterPosition();
|
||||
if (pos < 0 || pos >= mData.size()) return;
|
||||
final City data = mData.get(pos);
|
||||
if (data == null) return;
|
||||
GridListAdapter mAdapter = new GridListAdapter(mContext, mHotData);
|
||||
mAdapter.setInnerListener(mInnerListener);
|
||||
((HotViewHolder) holder).mRecyclerView.setAdapter(mAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mData == null ? 0 : mData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (mData == null || position >= mData.size()) return super.getItemViewType(position);
|
||||
City city = mData.get(position);
|
||||
if (city == null || city.getSection() == null) return super.getItemViewType(position);
|
||||
String section = city.getSection();
|
||||
if (section == null || section.length() < 1) return super.getItemViewType(position);
|
||||
if (position == 0 && TextUtils.equals("定", section.substring(0, 1)))
|
||||
return VIEW_TYPE_LOCATION;
|
||||
if (position == 1 && TextUtils.equals("热", section.substring(0, 1)))
|
||||
return VIEW_TYPE_HOT;
|
||||
return super.getItemViewType(position);
|
||||
}
|
||||
|
||||
public void setInnerListener(InnerListener listener){
|
||||
this.mInnerListener = listener;
|
||||
}
|
||||
|
||||
static class BaseViewHolder extends RecyclerView.ViewHolder{
|
||||
BaseViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultViewHolder extends BaseViewHolder{
|
||||
TextView name;
|
||||
|
||||
DefaultViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
name = itemView.findViewById(R.id.cp_list_item_name);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HotViewHolder extends BaseViewHolder {
|
||||
RecyclerView mRecyclerView;
|
||||
|
||||
HotViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
mRecyclerView = itemView.findViewById(R.id.cp_hot_list);
|
||||
mRecyclerView.setHasFixedSize(true);
|
||||
mRecyclerView.setLayoutManager(new GridLayoutManager(itemView.getContext(),
|
||||
GridListAdapter.SPAN_COUNT, LinearLayoutManager.VERTICAL, false));
|
||||
int space = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.cp_grid_item_space);
|
||||
mRecyclerView.addItemDecoration(new GridItemDecoration(GridListAdapter.SPAN_COUNT,
|
||||
space));
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocationViewHolder extends BaseViewHolder {
|
||||
FrameLayout container;
|
||||
TextView current;
|
||||
|
||||
LocationViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
container = itemView.findViewById(R.id.cp_list_item_location_layout);
|
||||
current = itemView.findViewById(R.id.cp_list_item_location);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.zaaach.citypicker.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zaaach.citypicker.R;
|
||||
import com.zaaach.citypicker.model.City;
|
||||
import com.zaaach.citypicker.model.HotCity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: Bro0cL
|
||||
* @Date: 2018/2/8 21:22
|
||||
*/
|
||||
public class GridListAdapter extends RecyclerView.Adapter<GridListAdapter.GridViewHolder>{
|
||||
public static final int SPAN_COUNT = 3;
|
||||
|
||||
private Context mContext;
|
||||
private List<HotCity> mData;
|
||||
private InnerListener mInnerListener;
|
||||
|
||||
public GridListAdapter(Context context, List<HotCity> data) {
|
||||
this.mContext = context;
|
||||
this.mData = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GridViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(mContext).inflate(R.layout.cp_grid_item_layout, parent, false);
|
||||
return new GridViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(GridViewHolder holder, int position) {
|
||||
final int pos = holder.getAdapterPosition();
|
||||
final City data = mData.get(pos);
|
||||
if (data == null) return;
|
||||
//设置item宽高
|
||||
DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
|
||||
int screenWidth = dm.widthPixels;
|
||||
TypedValue typedValue = new TypedValue();
|
||||
mContext.getTheme().resolveAttribute(R.attr.cpGridItemSpace, typedValue, true);
|
||||
int space = mContext.getResources().getDimensionPixelSize(typedValue.resourceId);
|
||||
int padding = mContext.getResources().getDimensionPixelSize(R.dimen.cp_default_padding);
|
||||
int indexBarWidth = mContext.getResources().getDimensionPixelSize(R.dimen.cp_index_bar_width);
|
||||
int itemWidth = (screenWidth - padding - space * (SPAN_COUNT - 1) - indexBarWidth) / SPAN_COUNT;
|
||||
ViewGroup.LayoutParams lp = holder.container.getLayoutParams();
|
||||
lp.width = itemWidth;
|
||||
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
holder.container.setLayoutParams(lp);
|
||||
|
||||
holder.name.setText(data.getName());
|
||||
holder.container.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mInnerListener != null){
|
||||
mInnerListener.dismiss(pos, data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mData == null ? 0 : mData.size();
|
||||
}
|
||||
|
||||
public static class GridViewHolder extends RecyclerView.ViewHolder{
|
||||
FrameLayout container;
|
||||
TextView name;
|
||||
|
||||
public GridViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
container = itemView.findViewById(R.id.cp_grid_item_layout);
|
||||
name = itemView.findViewById(R.id.cp_gird_item_name);
|
||||
}
|
||||
}
|
||||
|
||||
public void setInnerListener(InnerListener listener){
|
||||
this.mInnerListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.zaaach.citypicker.adapter;
|
||||
|
||||
import com.zaaach.citypicker.model.City;
|
||||
|
||||
public interface InnerListener {
|
||||
void dismiss(int position, City data);
|
||||
void locate();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.zaaach.citypicker.adapter;
|
||||
|
||||
import com.zaaach.citypicker.model.City;
|
||||
|
||||
public interface OnPickListener {
|
||||
void onPick(int position, City data);
|
||||
void onLocate();
|
||||
void onCancel();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.zaaach.citypicker.adapter.decoration;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import com.zaaach.citypicker.R;
|
||||
|
||||
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
|
||||
private float dividerHeight;
|
||||
private Paint mPaint;
|
||||
|
||||
public DividerItemDecoration(Context context) {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
TypedValue typedValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(R.attr.cpSectionBackground, typedValue, true);
|
||||
mPaint.setColor(context.getResources().getColor(typedValue.resourceId));
|
||||
dividerHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.5f, context.getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
super.getItemOffsets(outRect, view, parent, state);
|
||||
outRect.bottom = (int) dividerHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
int childCount = parent.getChildCount();
|
||||
int left = parent.getPaddingLeft();
|
||||
int right = parent.getWidth() - parent.getPaddingRight();
|
||||
|
||||
for (int i = 0; i < childCount - 1; i++) {
|
||||
View view = parent.getChildAt(i);
|
||||
float top = view.getBottom();
|
||||
float bottom = view.getBottom() + dividerHeight;
|
||||
c.drawRect(left, top, right, bottom, mPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.zaaach.citypicker.adapter.decoration;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
public class GridItemDecoration extends RecyclerView.ItemDecoration{
|
||||
private int mSpanCount;
|
||||
private int mSpace;
|
||||
|
||||
public GridItemDecoration(int spanCount, int space) {
|
||||
this.mSpanCount = spanCount;
|
||||
this.mSpace = space;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
int position = parent.getChildAdapterPosition(view);
|
||||
int column = position % mSpanCount;
|
||||
|
||||
outRect.left = column * mSpace / mSpanCount;
|
||||
outRect.right = mSpace - (column + 1) * mSpace / mSpanCount;
|
||||
if (position >= mSpanCount) {
|
||||
outRect.top = mSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.zaaach.citypicker.adapter.decoration;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.text.TextPaint;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import com.zaaach.citypicker.R;
|
||||
import com.zaaach.citypicker.model.City;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SectionItemDecoration extends RecyclerView.ItemDecoration {
|
||||
private List<City> mData;
|
||||
private Paint mBgPaint;
|
||||
private TextPaint mTextPaint;
|
||||
private Rect mBounds;
|
||||
|
||||
private int mSectionHeight;
|
||||
private int mBgColor;
|
||||
private int mTextColor;
|
||||
private int mTextSize;
|
||||
|
||||
public SectionItemDecoration(Context context, List<City> data) {
|
||||
this.mData = data;
|
||||
TypedValue typedValue = new TypedValue();
|
||||
|
||||
context.getTheme().resolveAttribute(R.attr.cpSectionBackground, typedValue, true);
|
||||
mBgColor = context.getResources().getColor(typedValue.resourceId);
|
||||
|
||||
context.getTheme().resolveAttribute(R.attr.cpSectionHeight, typedValue, true);
|
||||
mSectionHeight = context.getResources().getDimensionPixelSize(typedValue.resourceId);
|
||||
|
||||
context.getTheme().resolveAttribute(R.attr.cpSectionTextSize, typedValue, true);
|
||||
mTextSize = context.getResources().getDimensionPixelSize(typedValue.resourceId);
|
||||
|
||||
context.getTheme().resolveAttribute(R.attr.cpSectionTextColor, typedValue, true);
|
||||
mTextColor = context.getResources().getColor(typedValue.resourceId);
|
||||
|
||||
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mBgPaint.setColor(mBgColor);
|
||||
|
||||
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
mTextPaint.setTextSize(mTextSize);
|
||||
mTextPaint.setColor(mTextColor);
|
||||
|
||||
mBounds = new Rect();
|
||||
}
|
||||
|
||||
public void setData(List<City> data) {
|
||||
this.mData = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
super.onDraw(c, parent, state);
|
||||
final int left = parent.getPaddingLeft();
|
||||
final int right = parent.getWidth() - parent.getPaddingRight();
|
||||
final int childCount = parent.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
|
||||
.getLayoutParams();
|
||||
int position = params.getViewLayoutPosition();
|
||||
if (mData != null && !mData.isEmpty() && position <= mData.size() - 1 && position > -1) {
|
||||
if (position == 0) {
|
||||
drawSection(c, left, right, child, params, position);
|
||||
} else {
|
||||
if (null != mData.get(position).getSection()
|
||||
&& !mData.get(position).getSection().equals(mData.get(position - 1).getSection())) {
|
||||
drawSection(c, left, right, child, params, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawSection(Canvas c, int left, int right, View child,
|
||||
RecyclerView.LayoutParams params, int position) {
|
||||
c.drawRect(left,
|
||||
child.getTop() - params.topMargin - mSectionHeight,
|
||||
right,
|
||||
child.getTop() - params.topMargin, mBgPaint);
|
||||
mTextPaint.getTextBounds(mData.get(position).getSection(),
|
||||
0,
|
||||
mData.get(position).getSection().length(),
|
||||
mBounds);
|
||||
c.drawText(mData.get(position).getSection(),
|
||||
child.getPaddingLeft(),
|
||||
child.getTop() - params.topMargin - (mSectionHeight / 2 - mBounds.height() / 2),
|
||||
mTextPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
int pos = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
|
||||
if (pos < 0) return;
|
||||
if (mData == null || mData.isEmpty()) return;
|
||||
String section = mData.get(pos).getSection();
|
||||
View child = parent.findViewHolderForLayoutPosition(pos).itemView;
|
||||
|
||||
boolean flag = false;
|
||||
if ((pos + 1) < mData.size()) {
|
||||
if (null != section && !section.equals(mData.get(pos + 1).getSection())) {
|
||||
if (child.getHeight() + child.getTop() < mSectionHeight) {
|
||||
c.save();
|
||||
flag = true;
|
||||
c.translate(0, child.getHeight() + child.getTop() - mSectionHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
c.drawRect(parent.getPaddingLeft(),
|
||||
parent.getPaddingTop(),
|
||||
parent.getRight() - parent.getPaddingRight(),
|
||||
parent.getPaddingTop() + mSectionHeight, mBgPaint);
|
||||
mTextPaint.getTextBounds(section, 0, section.length(), mBounds);
|
||||
c.drawText(section,
|
||||
child.getPaddingLeft(),
|
||||
parent.getPaddingTop() + mSectionHeight - (mSectionHeight / 2 - mBounds.height() / 2),
|
||||
mTextPaint);
|
||||
if (flag)
|
||||
c.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
super.getItemOffsets(outRect, view, parent, state);
|
||||
int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
|
||||
if (mData != null && !mData.isEmpty() && position <= mData.size() - 1 && position > -1) {
|
||||
if (position == 0) {
|
||||
outRect.set(0, mSectionHeight, 0, 0);
|
||||
} else {
|
||||
if (null != mData.get(position).getSection()
|
||||
&& !mData.get(position).getSection().equals(mData.get(position - 1).getSection())) {
|
||||
outRect.set(0, mSectionHeight, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.zaaach.citypicker.db;
|
||||
|
||||
public class DBConfig {
|
||||
public static final String DB_NAME_V1 = "china_cities.db";
|
||||
public static final String DB_NAME_V2 = "china_cities_v2.db";
|
||||
public static final String LATEST_DB_NAME = DB_NAME_V2;
|
||||
|
||||
public static final String TABLE_NAME = "cities";
|
||||
|
||||
public static final String COLUMN_C_NAME = "c_name";
|
||||
public static final String COLUMN_C_PROVINCE = "c_province";
|
||||
public static final String COLUMN_C_PINYIN = "c_pinyin";
|
||||
public static final String COLUMN_C_CODE = "c_code";
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.zaaach.citypicker.db;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Environment;
|
||||
|
||||
import com.zaaach.citypicker.model.City;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.zaaach.citypicker.db.DBConfig.COLUMN_C_CODE;
|
||||
import static com.zaaach.citypicker.db.DBConfig.COLUMN_C_NAME;
|
||||
import static com.zaaach.citypicker.db.DBConfig.COLUMN_C_PINYIN;
|
||||
import static com.zaaach.citypicker.db.DBConfig.COLUMN_C_PROVINCE;
|
||||
import static com.zaaach.citypicker.db.DBConfig.LATEST_DB_NAME;
|
||||
import static com.zaaach.citypicker.db.DBConfig.DB_NAME_V1;
|
||||
import static com.zaaach.citypicker.db.DBConfig.TABLE_NAME;
|
||||
|
||||
/**
|
||||
* Author Bro0cL on 2016/1/26.
|
||||
*/
|
||||
public class DBManager {
|
||||
private static final int BUFFER_SIZE = 1024;
|
||||
|
||||
private String DB_PATH;
|
||||
private Context mContext;
|
||||
|
||||
public DBManager(Context context) {
|
||||
this.mContext = context;
|
||||
DB_PATH = File.separator + "data"
|
||||
+ Environment.getDataDirectory().getAbsolutePath() + File.separator
|
||||
+ context.getPackageName() + File.separator + "databases" + File.separator;
|
||||
copyDBFile();
|
||||
}
|
||||
|
||||
private void copyDBFile(){
|
||||
File dir = new File(DB_PATH);
|
||||
if (!dir.exists()){
|
||||
dir.mkdirs();
|
||||
}
|
||||
//如果旧版数据库存在,则删除
|
||||
File dbV1 = new File(DB_PATH + DB_NAME_V1);
|
||||
if (dbV1.exists()){
|
||||
dbV1.delete();
|
||||
}
|
||||
//创建新版本数据库
|
||||
File dbFile = new File(DB_PATH + LATEST_DB_NAME);
|
||||
if (!dbFile.exists()){
|
||||
InputStream is;
|
||||
OutputStream os;
|
||||
try {
|
||||
is = mContext.getResources().getAssets().open(LATEST_DB_NAME);
|
||||
os = new FileOutputStream(dbFile);
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int length;
|
||||
while ((length = is.read(buffer, 0, buffer.length)) > 0){
|
||||
os.write(buffer, 0, length);
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<City> getAllCities(){
|
||||
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH + LATEST_DB_NAME, null);
|
||||
Cursor cursor = db.rawQuery("select * from " + TABLE_NAME, null);
|
||||
List<City> result = new ArrayList<>();
|
||||
City city;
|
||||
while (cursor.moveToNext()){
|
||||
String name = cursor.getString(cursor.getColumnIndex(COLUMN_C_NAME));
|
||||
String province = cursor.getString(cursor.getColumnIndex(COLUMN_C_PROVINCE));
|
||||
String pinyin = cursor.getString(cursor.getColumnIndex(COLUMN_C_PINYIN));
|
||||
String code = cursor.getString(cursor.getColumnIndex(COLUMN_C_CODE));
|
||||
city = new City(name, province, pinyin, code);
|
||||
result.add(city);
|
||||
}
|
||||
cursor.close();
|
||||
db.close();
|
||||
Collections.sort(result, new CityComparator());
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<City> searchCity(final String keyword){
|
||||
String sql = "select * from " + TABLE_NAME + " where "
|
||||
+ COLUMN_C_NAME + " like ? " + "or "
|
||||
+ COLUMN_C_PINYIN + " like ? ";
|
||||
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH + LATEST_DB_NAME, null);
|
||||
Cursor cursor = db.rawQuery(sql, new String[]{"%"+keyword+"%", keyword+"%"});
|
||||
|
||||
List<City> result = new ArrayList<>();
|
||||
while (cursor.moveToNext()){
|
||||
String name = cursor.getString(cursor.getColumnIndex(COLUMN_C_NAME));
|
||||
String province = cursor.getString(cursor.getColumnIndex(COLUMN_C_PROVINCE));
|
||||
String pinyin = cursor.getString(cursor.getColumnIndex(COLUMN_C_PINYIN));
|
||||
String code = cursor.getString(cursor.getColumnIndex(COLUMN_C_CODE));
|
||||
City city = new City(name, province, pinyin, code);
|
||||
result.add(city);
|
||||
}
|
||||
cursor.close();
|
||||
db.close();
|
||||
CityComparator comparator = new CityComparator();
|
||||
Collections.sort(result, comparator);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* sort by a-z
|
||||
*/
|
||||
private class CityComparator implements Comparator<City>{
|
||||
@Override
|
||||
public int compare(City lhs, City rhs) {
|
||||
String a = lhs.getPinyin().substring(0, 1);
|
||||
String b = rhs.getPinyin().substring(0, 1);
|
||||
return a.compareTo(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.zaaach.citypicker.model;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* author Bro0cL on 2016/1/26.
|
||||
*/
|
||||
public class City {
|
||||
private String name;
|
||||
private String province;
|
||||
private String pinyin;
|
||||
private String code;
|
||||
|
||||
public City(String name, String province, String pinyin, String code) {
|
||||
this.name = name;
|
||||
this.province = province;
|
||||
this.pinyin = pinyin;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/***
|
||||
* 获取悬浮栏文本,(#、定位、热门 需要特殊处理)
|
||||
* @return
|
||||
*/
|
||||
public String getSection(){
|
||||
if (TextUtils.isEmpty(pinyin)) {
|
||||
return "#";
|
||||
} else {
|
||||
String c = pinyin.substring(0, 1);
|
||||
Pattern p = Pattern.compile("[a-zA-Z]");
|
||||
Matcher m = p.matcher(c);
|
||||
if (m.matches()) {
|
||||
return c.toUpperCase();
|
||||
}
|
||||
//在添加定位和热门数据时设置的section就是‘定’、’热‘开头
|
||||
else if (TextUtils.equals(c, "定") || TextUtils.equals(c, "热"))
|
||||
return pinyin;
|
||||
else
|
||||
return "#";
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPinyin() {
|
||||
return pinyin;
|
||||
}
|
||||
|
||||
public void setPinyin(String pinyin) {
|
||||
this.pinyin = pinyin;
|
||||
}
|
||||
|
||||
public String getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public void setProvince(String province) {
|
||||
this.province = province;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.zaaach.citypicker.model;
|
||||
|
||||
public class HotCity extends City {
|
||||
|
||||
public HotCity(String name, String province, String code) {
|
||||
super(name, province, "热门城市", code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.zaaach.citypicker.model;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public class LocateState {
|
||||
public static final int LOCATING = 123;
|
||||
public static final int SUCCESS = 132;
|
||||
public static final int FAILURE = 321;
|
||||
|
||||
@IntDef({SUCCESS, FAILURE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface State{}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.zaaach.citypicker.model;
|
||||
|
||||
public class LocatedCity extends City {
|
||||
|
||||
public LocatedCity(String name, String province, String code) {
|
||||
super(name, province, "定位城市", code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.zaaach.citypicker.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Display;
|
||||
import android.view.WindowManager;
|
||||
|
||||
/**
|
||||
* @Author: Bro0cL
|
||||
* @Date: 2018/12/4 11:35
|
||||
* @Discription:
|
||||
*/
|
||||
public class ScreenUtil {
|
||||
|
||||
private static int getInternalDimensionSize(Context context, String key) {
|
||||
int result = 0;
|
||||
try {
|
||||
int resourceId = context.getResources().getIdentifier(key, "dimen", "android");
|
||||
if (resourceId > 0) {
|
||||
result = Math.round(context.getResources().getDimensionPixelSize(resourceId) *
|
||||
Resources.getSystem().getDisplayMetrics().density /
|
||||
context.getResources().getDisplayMetrics().density);
|
||||
}
|
||||
} catch (Resources.NotFoundException ignored) {
|
||||
return 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int getStatusBarHeight(Context context){
|
||||
return getInternalDimensionSize(context, "status_bar_height");
|
||||
}
|
||||
|
||||
public static int getNavigationBarHeight(Context context) {
|
||||
boolean mInPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
|
||||
int result = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
if (hasNavBar((Activity) context)) {
|
||||
String key;
|
||||
if (mInPortrait) {
|
||||
key = "navigation_bar_height";
|
||||
} else {
|
||||
key = "navigation_bar_height_landscape";
|
||||
}
|
||||
return getInternalDimensionSize(context, key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean hasNavBar(Activity activity) {
|
||||
//判断小米手机是否开启了全面屏,开启了,直接返回false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
if (Settings.Global.getInt(activity.getContentResolver(), "force_fsg_nav_bar", 0) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//其他手机根据屏幕真实高度与显示高度是否相同来判断
|
||||
WindowManager windowManager = activity.getWindowManager();
|
||||
Display d = windowManager.getDefaultDisplay();
|
||||
|
||||
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
d.getRealMetrics(realDisplayMetrics);
|
||||
}
|
||||
|
||||
int realHeight = realDisplayMetrics.heightPixels;
|
||||
int realWidth = realDisplayMetrics.widthPixels;
|
||||
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
d.getMetrics(displayMetrics);
|
||||
|
||||
int displayHeight = displayMetrics.heightPixels;
|
||||
int displayWidth = displayMetrics.widthPixels;
|
||||
|
||||
return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package com.zaaach.citypicker.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zaaach.citypicker.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: Bro0cL
|
||||
* @Date: 2018/2/8 10:56
|
||||
*/
|
||||
public class SideIndexBar extends View{
|
||||
private static final String[] DEFAULT_INDEX_ITEMS = {"定位", "热门", "A", "B", "C", "D", "E", "F", "G", "H",
|
||||
"I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"};
|
||||
|
||||
private List<String> mIndexItems;
|
||||
private float mItemHeight; //每个index的高度
|
||||
private int mTextSize; //sp
|
||||
private int mTextColor;
|
||||
private int mTextTouchedColor;
|
||||
private int mCurrentIndex = -1;
|
||||
|
||||
private Paint mPaint;
|
||||
private Paint mTouchedPaint;
|
||||
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private float mTopMargin; //居中绘制,文字绘制起点和控件顶部的间隔
|
||||
|
||||
private TextView mOverlayTextView;
|
||||
private OnIndexTouchedChangedListener mOnIndexChangedListener;
|
||||
|
||||
private int navigationBarHeight;
|
||||
|
||||
public void setNavigationBarHeight(int height){
|
||||
this.navigationBarHeight = height;
|
||||
}
|
||||
|
||||
public SideIndexBar(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SideIndexBar(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public SideIndexBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mIndexItems = new ArrayList<>();
|
||||
mIndexItems.addAll(Arrays.asList(DEFAULT_INDEX_ITEMS));
|
||||
|
||||
TypedValue typedValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(R.attr.cpIndexBarTextSize, typedValue, true);
|
||||
mTextSize = context.getResources().getDimensionPixelSize(typedValue.resourceId);
|
||||
|
||||
context.getTheme().resolveAttribute(R.attr.cpIndexBarNormalTextColor, typedValue, true);
|
||||
mTextColor = context.getResources().getColor(typedValue.resourceId);
|
||||
|
||||
context.getTheme().resolveAttribute(R.attr.cpIndexBarSelectedTextColor, typedValue, true);
|
||||
mTextTouchedColor = context.getResources().getColor(typedValue.resourceId);
|
||||
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setTextSize(mTextSize);
|
||||
mPaint.setColor(mTextColor);
|
||||
|
||||
mTouchedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mTouchedPaint.setTextSize(mTextSize);
|
||||
mTouchedPaint.setColor(mTextTouchedColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
String index;
|
||||
for (int i = 0; i < mIndexItems.size(); i++) {
|
||||
index = mIndexItems.get(i);
|
||||
Paint.FontMetrics fm = mPaint.getFontMetrics();
|
||||
canvas.drawText(index,
|
||||
(mWidth - mPaint.measureText(index)) / 2,
|
||||
mItemHeight / 2 + (fm.bottom-fm.top) / 2 - fm.bottom + mItemHeight * i + mTopMargin,
|
||||
i == mCurrentIndex ? mTouchedPaint : mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
mWidth = getWidth();
|
||||
if (Math.abs(h - oldh) == navigationBarHeight){
|
||||
//底部导航栏隐藏或显示
|
||||
mHeight = h;
|
||||
}else {
|
||||
//避免软键盘弹出时挤压
|
||||
mHeight = Math.max(getHeight(), oldh);
|
||||
}
|
||||
mItemHeight = mHeight / mIndexItems.size();
|
||||
mTopMargin = (mHeight - mItemHeight * mIndexItems.size()) / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
return super.performClick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
performClick();
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
float y = event.getY();
|
||||
int indexSize = mIndexItems.size();
|
||||
int touchedIndex = (int) (y / mItemHeight);
|
||||
if (touchedIndex < 0) {
|
||||
touchedIndex = 0;
|
||||
}else if (touchedIndex >= indexSize) {
|
||||
touchedIndex = indexSize - 1;
|
||||
}
|
||||
if (mOnIndexChangedListener != null && touchedIndex >= 0 && touchedIndex < indexSize){
|
||||
if (touchedIndex != mCurrentIndex) {
|
||||
mCurrentIndex = touchedIndex;
|
||||
if (mOverlayTextView != null){
|
||||
mOverlayTextView.setVisibility(VISIBLE);
|
||||
mOverlayTextView.setText(mIndexItems.get(touchedIndex));
|
||||
}
|
||||
mOnIndexChangedListener.onIndexChanged(mIndexItems.get(touchedIndex), touchedIndex);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mCurrentIndex = -1;
|
||||
if (mOverlayTextView != null){
|
||||
mOverlayTextView.setVisibility(GONE);
|
||||
}
|
||||
invalidate();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public SideIndexBar setOverlayTextView(TextView overlay){
|
||||
this.mOverlayTextView = overlay;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SideIndexBar setOnIndexChangedListener(OnIndexTouchedChangedListener listener){
|
||||
this.mOnIndexChangedListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public interface OnIndexTouchedChangedListener{
|
||||
void onIndexChanged(String index, int position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:fromYDelta="100%p"
|
||||
android:toYDelta="0"
|
||||
android:duration="300"/>
|
||||
</set>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="100%p"
|
||||
android:duration="300"/>
|
||||
</set>
|
||||
11
peizhen/citypicker/src/main/res/drawable/cp_grid_item_bg.xml
Normal file
11
peizhen/citypicker/src/main/res/drawable/cp_grid_item_bg.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/cp_color_grid_item_bg" />
|
||||
<corners
|
||||
android:bottomLeftRadius="4dp"
|
||||
android:bottomRightRadius="4dp"
|
||||
android:radius="4dp"
|
||||
android:topLeftRadius="4dp"
|
||||
android:topRightRadius="4dp"/>
|
||||
</shape>
|
||||
12
peizhen/citypicker/src/main/res/drawable/cp_overlay_bg.xml
Normal file
12
peizhen/citypicker/src/main/res/drawable/cp_overlay_bg.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/cp_color_gray_dark" />
|
||||
|
||||
<corners
|
||||
android:bottomLeftRadius="6dp"
|
||||
android:bottomRightRadius="6dp"
|
||||
android:radius="6dp"
|
||||
android:topLeftRadius="6dp"
|
||||
android:topRightRadius="6dp" />
|
||||
</shape>
|
||||
BIN
peizhen/citypicker/src/main/res/drawable/icon_back.png
Normal file
BIN
peizhen/citypicker/src/main/res/drawable/icon_back.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#fff6f6f6" />
|
||||
<corners android:topLeftRadius="18dp" android:topRightRadius="18dp" android:bottomLeftRadius="18dp" android:bottomRightRadius="18dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,86 @@
|
||||
<?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:orientation="vertical">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="#ffffff"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl_back"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="21dp"
|
||||
android:layout_height="21dp"
|
||||
android:background="@drawable/icon_back"
|
||||
android:textColor="#000000" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:text="城市选择"
|
||||
android:textColor="#000000"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/right"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textColor="#ff333333"
|
||||
android:textSize="14sp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="gone"
|
||||
android:layout_marginRight="13dp"/>
|
||||
|
||||
</RelativeLayout>
|
||||
<include layout="@layout/cp_search_view" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/cp_city_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cp_overlay"
|
||||
android:layout_width="?attr/cpOverlayWidth"
|
||||
android:layout_height="?attr/cpOverlayHeight"
|
||||
android:visibility="gone"
|
||||
android:gravity="center"
|
||||
android:layout_centerInParent="true"
|
||||
android:textSize="?attr/cpOverlayTextSize"
|
||||
android:textColor="?attr/cpOverlayTextColor"
|
||||
android:background="?attr/cpOverlayBackground"/>
|
||||
|
||||
<!--右侧字母导航-->
|
||||
<com.zaaach.citypicker.view.SideIndexBar
|
||||
android:id="@+id/cp_side_index_bar"
|
||||
android:layout_width="@dimen/cp_index_bar_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
|
||||
<include layout="@layout/cp_empty_view"/>
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
30
peizhen/citypicker/src/main/res/layout/cp_empty_view.xml
Normal file
30
peizhen/citypicker/src/main/res/layout/cp_empty_view.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/cp_empty_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
android:background="@android:color/white"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="108dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cp_no_result_icon"
|
||||
android:layout_width="?attr/cpEmptyIconWidth"
|
||||
android:layout_height="?attr/cpEmptyIconHeight"
|
||||
android:src="?attr/cpEmptyIcon"
|
||||
android:scaleType="fitCenter"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cp_no_result_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="?attr/cpEmptyText"
|
||||
android:textSize="?attr/cpEmptyTextSize"
|
||||
android:textColor="?attr/cpEmptyTextColor"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/cp_grid_item_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="?attr/cpListItemHeight"
|
||||
android:foreground="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cp_gird_item_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/cp_grid_item_padding"
|
||||
android:paddingBottom="@dimen/cp_grid_item_padding"
|
||||
android:gravity="center"
|
||||
android:textSize="?attr/cpListItemTextSize"
|
||||
android:textColor="?attr/cpListItemTextColor"
|
||||
android:background="?attr/cpGridItemBackground"/>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/cp_list_item_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/cpListItemHeight"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:textColor="?attr/cpListItemTextColor"
|
||||
android:textSize="?attr/cpListItemTextSize"
|
||||
tools:ignore="RtlSymmetry" />
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/cp_hot_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:overScrollMode="never"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingRight="@dimen/cp_index_bar_width"
|
||||
android:paddingEnd="@dimen/cp_index_bar_width"
|
||||
android:paddingStart="16dp" />
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/cp_list_item_location_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="?attr/cpListItemHeight"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
tools:ignore="UselessParent">
|
||||
<TextView
|
||||
android:id="@+id/cp_list_item_location"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/cp_grid_item_padding"
|
||||
android:paddingBottom="@dimen/cp_grid_item_padding"
|
||||
android:gravity="center"
|
||||
android:background="?attr/cpGridItemBackground"
|
||||
android:textSize="?attr/cpListItemTextSize"
|
||||
android:textColor="?attr/cpListItemTextColor"/>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
29
peizhen/citypicker/src/main/res/layout/cp_search_view.xml
Normal file
29
peizhen/citypicker/src/main/res/layout/cp_search_view.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/cp_search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:background="@android:color/white"
|
||||
|
||||
tools:showIn="@layout/cp_dialog_city_picker">
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/cp_search_box"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginLeft="22.5dp"
|
||||
android:layout_marginRight="22.5dp"
|
||||
android:gravity="center"
|
||||
android:layout_height="37dp"
|
||||
android:background="@drawable/shape_sousuo_bg"
|
||||
android:hint="搜索城市名"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#ff999999"
|
||||
android:imeActionLabel="搜索"
|
||||
android:imeOptions="actionSend"
|
||||
android:inputType="text"
|
||||
android:singleLine="true"
|
||||
android:text="" />
|
||||
|
||||
</RelativeLayout>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
peizhen/citypicker/src/main/res/mipmap-xxhdpi/cp_icon_empty.png
Normal file
BIN
peizhen/citypicker/src/main/res/mipmap-xxhdpi/cp_icon_empty.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
6
peizhen/citypicker/src/main/res/values-w820dp/dimens.xml
Normal file
6
peizhen/citypicker/src/main/res/values-w820dp/dimens.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
||||
41
peizhen/citypicker/src/main/res/values/attrs.xml
Normal file
41
peizhen/citypicker/src/main/res/values/attrs.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<attr name="cpCancelTextSize" format="dimension|reference" />
|
||||
<attr name="cpCancelTextColor" format="color|reference" />
|
||||
|
||||
<attr name="cpClearTextIcon" format="reference" />
|
||||
<attr name="cpSearchTextSize" format="dimension|reference" />
|
||||
<attr name="cpSearchTextColor" format="color|reference" />
|
||||
<attr name="cpSearchHintText" format="string|reference" />
|
||||
<attr name="cpSearchHintTextColor" format="color|reference" />
|
||||
<attr name="cpSearchCursorDrawable" format="reference" />
|
||||
|
||||
<attr name="cpListItemTextSize" format="dimension|reference" />
|
||||
<attr name="cpListItemTextColor" format="color|reference" />
|
||||
<attr name="cpListItemHeight" format="dimension|reference"/>
|
||||
|
||||
<attr name="cpEmptyIcon" format="reference"/>
|
||||
<attr name="cpEmptyIconWidth" format="dimension|reference"/>
|
||||
<attr name="cpEmptyIconHeight" format="dimension|reference"/>
|
||||
<attr name="cpEmptyText" format="string|reference"/>
|
||||
<attr name="cpEmptyTextSize" format="dimension|reference"/>
|
||||
<attr name="cpEmptyTextColor" format="color|reference"/>
|
||||
|
||||
<attr name="cpGridItemBackground" format="color|reference"/>
|
||||
<attr name="cpGridItemSpace" format="reference"/>
|
||||
|
||||
<attr name="cpSectionHeight" format="reference"/>
|
||||
<attr name="cpSectionTextSize" format="reference" />
|
||||
<attr name="cpSectionTextColor" format="reference" />
|
||||
<attr name="cpSectionBackground" format="reference" />
|
||||
|
||||
<attr name="cpIndexBarTextSize" format="reference" />
|
||||
<attr name="cpIndexBarNormalTextColor" format="reference" />
|
||||
<attr name="cpIndexBarSelectedTextColor" format="reference" />
|
||||
|
||||
<attr name="cpOverlayWidth" format="dimension|reference"/>
|
||||
<attr name="cpOverlayHeight" format="dimension|reference"/>
|
||||
<attr name="cpOverlayTextSize" format="dimension|reference"/>
|
||||
<attr name="cpOverlayTextColor" format="color|reference"/>
|
||||
<attr name="cpOverlayBackground" format="color|reference"/>
|
||||
</resources>
|
||||
13
peizhen/citypicker/src/main/res/values/colors.xml
Normal file
13
peizhen/citypicker/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="cp_colorPrimary">#3F51B5</color>
|
||||
<color name="cp_colorPrimaryDark">#303F9F</color>
|
||||
<color name="cp_colorAccent">#3F51B5</color>
|
||||
|
||||
<color name="cp_color_gray_light">#cdcdcd</color>
|
||||
<color name="cp_color_gray">#999999</color>
|
||||
<color name="cp_color_gray_deep">#666666</color>
|
||||
<color name="cp_color_gray_dark">#333333</color>
|
||||
<color name="cp_color_section_bg">#EDEDED</color>
|
||||
<color name="cp_color_grid_item_bg">#f5f5f5</color>
|
||||
</resources>
|
||||
27
peizhen/citypicker/src/main/res/values/dimens.xml
Normal file
27
peizhen/citypicker/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="cp_default_padding">16dp</dimen>
|
||||
|
||||
<dimen name="cp_index_bar_width">36dp</dimen>
|
||||
<dimen name="cp_index_bar_text_size">13sp</dimen>
|
||||
|
||||
<dimen name="cp_cancel_text_size">15sp</dimen>
|
||||
<dimen name="cp_search_text_size">15sp</dimen>
|
||||
|
||||
<dimen name="cp_empty_icon_width">56dp</dimen>
|
||||
<dimen name="cp_empty_icon_height">56dp</dimen>
|
||||
<dimen name="cp_empty_text_size">14sp</dimen>
|
||||
|
||||
<dimen name="cp_section_height">32dp</dimen>
|
||||
<dimen name="cp_section_text_size">15sp</dimen>
|
||||
|
||||
<dimen name="cp_list_item_height">50dp</dimen>
|
||||
<dimen name="cp_list_item_text_size">15sp</dimen>
|
||||
|
||||
<dimen name="cp_grid_item_space">16dp</dimen>
|
||||
<dimen name="cp_grid_item_padding">10dp</dimen>
|
||||
|
||||
<dimen name="cp_overlay_width">100dp</dimen>
|
||||
<dimen name="cp_overlay_height">100dp</dimen>
|
||||
<dimen name="cp_overlay_text_size">28sp</dimen>
|
||||
</resources>
|
||||
8
peizhen/citypicker/src/main/res/values/strings.xml
Normal file
8
peizhen/citypicker/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
<string name="app_name">CityPicker</string>
|
||||
<string name="cp_search_hint_text">城市中文名或拼音</string>
|
||||
<string name="cp_cancel">取消</string>
|
||||
<string name="cp_no_result">没有找到,换个词试试~</string>
|
||||
<string name="cp_locate_failed">定位失败</string>
|
||||
<string name="cp_locating">正在定位…</string>
|
||||
</resources>
|
||||
61
peizhen/citypicker/src/main/res/values/styles.xml
Normal file
61
peizhen/citypicker/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/cp_colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/cp_colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/cp_colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="CityPickerStyle" parent="AlertDialog.AppCompat">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
</style>
|
||||
|
||||
<style name="DefaultCityPickerAnimation" parent="@android:style/Animation">
|
||||
<item name="android:windowEnterAnimation">@anim/cp_push_bottom_in</item>
|
||||
<item name="android:windowExitAnimation">@anim/cp_push_bottom_out</item>
|
||||
</style>
|
||||
|
||||
<style name="DefaultCityPickerTheme" parent="AppTheme">
|
||||
<item name="cpCancelTextSize">@dimen/cp_cancel_text_size</item>
|
||||
<item name="cpCancelTextColor">@color/cp_color_gray</item>
|
||||
|
||||
<item name="cpClearTextIcon">@mipmap/cp_icon_clear_all</item>
|
||||
<item name="cpSearchTextSize">@dimen/cp_search_text_size</item>
|
||||
<item name="cpSearchTextColor">@color/cp_color_gray_dark</item>
|
||||
<item name="cpSearchHintText">@string/cp_search_hint_text</item>
|
||||
<item name="cpSearchHintTextColor">@color/cp_color_gray_light</item>
|
||||
<item name="cpSearchCursorDrawable">@color/cp_color_gray_dark</item>
|
||||
|
||||
<item name="cpListItemHeight">@dimen/cp_list_item_height</item>
|
||||
<item name="cpListItemTextSize">@dimen/cp_list_item_text_size</item>
|
||||
<item name="cpListItemTextColor">@color/cp_color_gray_deep</item>
|
||||
|
||||
<item name="cpEmptyIcon">@mipmap/cp_icon_empty</item>
|
||||
<item name="cpEmptyIconWidth">@dimen/cp_empty_icon_width</item>
|
||||
<item name="cpEmptyIconHeight">@dimen/cp_empty_icon_height</item>
|
||||
<item name="cpEmptyText">@string/cp_no_result</item>
|
||||
<item name="cpEmptyTextSize">@dimen/cp_empty_text_size</item>
|
||||
<item name="cpEmptyTextColor">@color/cp_color_gray_deep</item>
|
||||
|
||||
<item name="cpGridItemBackground">@drawable/cp_grid_item_bg</item>
|
||||
<item name="cpGridItemSpace">@dimen/cp_grid_item_space</item>
|
||||
|
||||
<item name="cpSectionHeight">@dimen/cp_section_height</item>
|
||||
<item name="cpSectionTextSize">@dimen/cp_section_text_size</item>
|
||||
<item name="cpSectionTextColor">@color/cp_color_gray</item>
|
||||
<item name="cpSectionBackground">@color/cp_color_section_bg</item>
|
||||
|
||||
<item name="cpIndexBarTextSize">@dimen/cp_index_bar_text_size</item>
|
||||
<item name="cpIndexBarNormalTextColor">@color/cp_color_gray</item>
|
||||
<item name="cpIndexBarSelectedTextColor">@color/cp_color_gray_dark</item>
|
||||
|
||||
<item name="cpOverlayWidth">@dimen/cp_overlay_width</item>
|
||||
<item name="cpOverlayHeight">@dimen/cp_overlay_height</item>
|
||||
<item name="cpOverlayTextSize">@dimen/cp_overlay_text_size</item>
|
||||
<item name="cpOverlayTextColor">@android:color/white</item>
|
||||
<item name="cpOverlayBackground">@drawable/cp_overlay_bg</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.zaaach.citypicker;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user