fup?cz$ofXSST4Zg*v1T|);f{6JiL}TiF3)%_dQ3Vk*9#U89Nty&+$*Ft_
z#6nn%a+f^`EXk>x@>xVE{18?0a^m4C0n0;TaK?Qb{26-3OM
z5RDo2$%Kps&8wk;h}mc|=i-hZGMMv#jTQ7s&=8_AH+>uV1@UGDeG)W=oVu|%?T}~M
zVIbrapayuW0{qJxA-&T5tSO~|3xShWz=sv!Uq0sf@Yst$+HY@f2j~pB_aHM1J}yW9
z@@dGabMbh5arwlxe6qOU=2&p^A)__Gw;^h^hfKJ7)Ue%T3!=@q$PiOqT@9H9sH=es
ztAV2tB<85emW!*v1>@qGDfRXBVW}-aBqJ6E(&~rPkmO4`<1nei
zWlYLphY6jLfxG$hbvZmm4W>J3{D3C9NwM&Kd}S-se!eVM$xcC;)ns
zMY4xsU(o^3lDKr~Qr4tSIU2lY5!VZyoK2Gc%#+~do2;N~-w
zC$Vd3zLda%sl$@$ED1^upHpsWIoBkuER+gZY$DfMNp~WU$aP23`dKG(1<0w-qdDb}
z`gcvzdY7}EZL;VJNk5lqvgpb=PZkY~ab}WU+&t4f>lx%>H!q0v;#nl5-?iw-#%vDu
z3nrv{*2Xbwv!n^>^Sy<^IGLEYD-^$lL4z%EyleIIvW3CorWg5U%NMsvDZ&0RQQcT)
kaVw@$_?SW{=gqtR1FV9AnH2u#F8}}l07*qoM6N<$g7ShnjQ{`u
literal 0
HcmV?d00001
diff --git a/FilePicker/README.md b/FilePicker/README.md
new file mode 100644
index 00000000..572ff04b
--- /dev/null
+++ b/FilePicker/README.md
@@ -0,0 +1,2 @@
+# 文件/目录选择器
+
diff --git a/FilePicker/build.gradle b/FilePicker/build.gradle
new file mode 100644
index 00000000..2b8b68b9
--- /dev/null
+++ b/FilePicker/build.gradle
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+apply from: "${rootDir}/gradle/library.gradle"
+apply from: "${rootDir}/gradle/publish.gradle"
+
+dependencies {
+ implementation androidxLibrary.annotation
+ implementation androidxLibrary.recyclerview
+ implementation project(':BasePicker')
+}
diff --git a/FilePicker/consumer-rules.pro b/FilePicker/consumer-rules.pro
new file mode 100644
index 00000000..057cd949
--- /dev/null
+++ b/FilePicker/consumer-rules.pro
@@ -0,0 +1 @@
+# 本库模块专用的混淆规则
diff --git a/FilePicker/src/main/AndroidManifest.xml b/FilePicker/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..e159d568
--- /dev/null
+++ b/FilePicker/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/FileExplorer.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/FileExplorer.java
new file mode 100644
index 00000000..75c1bd36
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/FileExplorer.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.github.gzuliyujiang.basepicker.PickerLog;
+import com.github.gzuliyujiang.filepicker.adapter.FileAdapter;
+import com.github.gzuliyujiang.filepicker.adapter.FileEntity;
+import com.github.gzuliyujiang.filepicker.adapter.PathAdapter;
+import com.github.gzuliyujiang.filepicker.annotation.ExplorerMode;
+import com.github.gzuliyujiang.filepicker.contract.OnFileClickedListener;
+import com.github.gzuliyujiang.filepicker.contract.OnPathClickedListener;
+
+import java.io.File;
+import java.util.Locale;
+
+/**
+ * 文件浏览器
+ *
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2021/6/10 18:50
+ */
+@SuppressWarnings("unused")
+public class FileExplorer extends FrameLayout {
+ private int explorerMode = ExplorerMode.FILE;
+ private File initDir;
+ private CharSequence emptyHint;
+ private FileAdapter fileAdapter;
+ private PathAdapter pathAdapter;
+ private RecyclerView fileListView;
+ private TextView emptyHintView;
+ private View bottomLineView;
+ private RecyclerView pathListView;
+ private OnFileClickedListener onFileClickedListener;
+
+ public FileExplorer(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public FileExplorer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public FileExplorer(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context);
+ }
+
+ public FileExplorer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context);
+ }
+
+ private void init(Context context) {
+ View contentView = inflate(context, R.layout.file_picker_content, this);
+ fileListView = contentView.findViewById(R.id.file_picker_file_list);
+ fileAdapter = new FileAdapter(context);
+ fileListView.setAdapter(fileAdapter);
+ emptyHint = Locale.getDefault().getDisplayLanguage().contains("中文") ? "<空>" : "";
+ emptyHintView = contentView.findViewById(R.id.file_picker_empty_hint);
+ emptyHintView.setText(emptyHint);
+ bottomLineView = contentView.findViewById(R.id.file_picker_bottom_line);
+ pathAdapter = new PathAdapter(context);
+ pathListView = contentView.findViewById(R.id.file_picker_path_list);
+ pathListView.setAdapter(pathAdapter);
+ fileAdapter.setOnlyListDir(false);
+ fileAdapter.setShowHideDir(true);
+ fileAdapter.setShowHomeDir(true);
+ fileAdapter.setShowUpDir(true);
+ initDir = getDefaultDir();
+ fileAdapter.loadData(initDir);
+ fileAdapter.setOnPathClickedListener(new OnPathClickedListener() {
+ @Override
+ public void onPathClicked(int position, @NonNull String path) {
+ FileEntity entity = fileAdapter.getItem(position);
+ PickerLog.print("clicked file item: " + entity);
+ File file = entity.getFile();
+ if (file.isDirectory()) {
+ refreshCurrent(file);
+ return;
+ }
+ if (onFileClickedListener != null) {
+ onFileClickedListener.onFileClicked(file);
+ }
+ }
+ });
+ pathAdapter.setOnPathClickedListener(new OnPathClickedListener() {
+ @Override
+ public void onPathClicked(int position, @NonNull String path) {
+ PickerLog.print("clicked path name: " + path);
+ refreshCurrent(new File(path));
+ }
+ });
+ }
+
+ public final File getDefaultDir() {
+ if (initDir != null) {
+ return initDir;
+ }
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ return getContext().getExternalFilesDir(null);
+ } else {
+ return getContext().getFilesDir();
+ }
+ }
+
+ public void setInitDir(@ExplorerMode int explorerMode, File initDir) {
+ if (this.explorerMode != explorerMode) {
+ this.explorerMode = explorerMode;
+ fileAdapter.setOnlyListDir(explorerMode == ExplorerMode.DIRECTORY);
+ }
+ if (initDir == null) {
+ initDir = getDefaultDir();
+ }
+ this.initDir = initDir;
+ refreshCurrent(initDir);
+ }
+
+ @ExplorerMode
+ public final int getExplorerMode() {
+ return explorerMode;
+ }
+
+ public void setEmptyHint(@NonNull CharSequence emptyHint) {
+ if (TextUtils.equals(this.emptyHint, emptyHint)) {
+ return;
+ }
+ this.emptyHint = emptyHint;
+ emptyHintView.setText(emptyHint);
+ }
+
+ public final void refreshCurrent(File current) {
+ if (current == null) {
+ return;
+ }
+ pathAdapter.updatePath(current);
+ fileAdapter.loadData(current);
+ int itemCount = fileAdapter.getItemCount();
+ if (fileAdapter.isShowHomeDir()) {
+ itemCount--;
+ }
+ if (fileAdapter.isShowUpDir()) {
+ itemCount--;
+ }
+ if (itemCount < 1) {
+ PickerLog.print("no files, or dir is empty");
+ emptyHintView.setVisibility(View.VISIBLE);
+ emptyHintView.setText(emptyHint);
+ } else {
+ PickerLog.print("files or dirs count: " + itemCount);
+ emptyHintView.setVisibility(View.GONE);
+ }
+ }
+
+ public void setOnFileClickedListener(OnFileClickedListener listener) {
+ this.onFileClickedListener = listener;
+ }
+
+ public void setAllowExtensions(String[] allowExtensions) {
+ fileAdapter.setAllowExtensions(allowExtensions);
+ }
+
+ public void setShowUpDir(boolean showUpDir) {
+ fileAdapter.setShowUpDir(showUpDir);
+ }
+
+ public void setShowHomeDir(boolean showHomeDir) {
+ fileAdapter.setShowHomeDir(showHomeDir);
+ }
+
+ public void setShowHideDir(boolean showHideDir) {
+ fileAdapter.setShowHideDir(showHideDir);
+ }
+
+ public void setFileIcon(Drawable fileIcon) {
+ fileAdapter.setFileIcon(fileIcon);
+ }
+
+ public void setFolderIcon(Drawable folderIcon) {
+ fileAdapter.setFolderIcon(folderIcon);
+ }
+
+ public void setHomeIcon(Drawable homeIcon) {
+ fileAdapter.setHomeIcon(homeIcon);
+ }
+
+ public void setUpIcon(Drawable upIcon) {
+ fileAdapter.setUpIcon(upIcon);
+ }
+
+ public void setArrowIcon(Drawable arrowIcon) {
+ pathAdapter.setArrowIcon(arrowIcon);
+ }
+
+ public void setItemHeight(int itemHeight) {
+ fileAdapter.setItemHeight(itemHeight);
+ }
+
+ public final FileAdapter getFileAdapter() {
+ return fileAdapter;
+ }
+
+ public final PathAdapter getPathAdapter() {
+ return pathAdapter;
+ }
+
+ public final File getCurrentFile() {
+ return fileAdapter.getCurrentFile();
+ }
+
+ public final RecyclerView getFileListView() {
+ return fileListView;
+ }
+
+ public final TextView getEmptyHintView() {
+ return emptyHintView;
+ }
+
+ public final View getBottomLineView() {
+ return bottomLineView;
+ }
+
+ public final RecyclerView getPathListView() {
+ return pathListView;
+ }
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/FilePicker.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/FilePicker.java
new file mode 100644
index 00000000..973593fd
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/FilePicker.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker;
+
+import android.app.Activity;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.StyleRes;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.github.gzuliyujiang.basepicker.ConfirmPicker;
+import com.github.gzuliyujiang.basepicker.PickerLog;
+import com.github.gzuliyujiang.filepicker.annotation.ExplorerMode;
+import com.github.gzuliyujiang.filepicker.contract.OnFileClickedListener;
+import com.github.gzuliyujiang.filepicker.contract.OnFilePickedListener;
+
+import java.io.File;
+
+/**
+ * 文件目录选择器
+ *
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2015/9/29
+ */
+@SuppressWarnings("unused")
+public class FilePicker extends ConfirmPicker {
+ private int explorerMode = ExplorerMode.FILE;
+ private File initDir;
+ private FileExplorer fileExplorer;
+ private OnFilePickedListener onFilePickedListener;
+ private boolean initialized = false;
+
+ public FilePicker(Activity activity) {
+ super(activity);
+ }
+
+ public FilePicker(@NonNull Activity activity, @StyleRes int themeResId) {
+ super(activity, themeResId);
+ }
+
+ @NonNull
+ @Override
+ protected View createBodyView(@NonNull Activity activity) {
+ fileExplorer = new FileExplorer(activity);
+ return fileExplorer;
+ }
+
+ @Override
+ protected void initData() {
+ super.initData();
+ initialized = true;
+ if (explorerMode == ExplorerMode.FILE) {
+ okView.setVisibility(View.INVISIBLE);
+ }
+ setBodyHeight(300);
+ setInitDir(explorerMode, initDir);
+ }
+
+ @Override
+ protected void onCancel() {
+
+ }
+
+ @Override
+ protected void onOk() {
+ File currentFile = fileExplorer.getCurrentFile();
+ PickerLog.print("picked directory: " + currentFile);
+ if (onFilePickedListener != null) {
+ onFilePickedListener.onFilePicked(currentFile);
+ }
+ }
+
+ public void setInitDir(@ExplorerMode int explorerMode, File initDir) {
+ this.explorerMode = explorerMode;
+ this.initDir = initDir;
+ if (initialized) {
+ fileExplorer.setInitDir(explorerMode, initDir);
+ }
+ }
+
+ public void setOnFilePickedListener(OnFilePickedListener listener) {
+ this.onFilePickedListener = listener;
+ fileExplorer.setOnFileClickedListener(new OnFileClickedListener() {
+ @Override
+ public void onFileClicked(@NonNull File file) {
+ if (explorerMode == ExplorerMode.FILE) {
+ dismiss();
+ onFilePickedListener.onFilePicked(file);
+ }
+ }
+ });
+ }
+
+ public final File getCurrentFile() {
+ return fileExplorer.getCurrentFile();
+ }
+
+ public final FileExplorer getFileExplorer() {
+ return fileExplorer;
+ }
+
+ public final RecyclerView getFileListView() {
+ return fileExplorer.getFileListView();
+ }
+
+ public final TextView getEmptyHintView() {
+ return fileExplorer.getEmptyHintView();
+ }
+
+ public final View getBottomLineView() {
+ return fileExplorer.getBottomLineView();
+ }
+
+ public final RecyclerView getPathListView() {
+ return fileExplorer.getPathListView();
+ }
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/FileAdapter.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/FileAdapter.java
new file mode 100644
index 00000000..8625296d
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/FileAdapter.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.adapter;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Dimension;
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.github.gzuliyujiang.basepicker.PickerLog;
+import com.github.gzuliyujiang.filepicker.R;
+import com.github.gzuliyujiang.filepicker.annotation.FileSort;
+import com.github.gzuliyujiang.filepicker.contract.OnPathClickedListener;
+import com.github.gzuliyujiang.filepicker.filter.SimpleFilter;
+import com.github.gzuliyujiang.filepicker.sort.SortByExtension;
+import com.github.gzuliyujiang.filepicker.sort.SortByName;
+import com.github.gzuliyujiang.filepicker.sort.SortBySize;
+import com.github.gzuliyujiang.filepicker.sort.SortByTime;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 文件目录数据适配
+ *
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2014/05/23 18:02
+ */
+@SuppressWarnings("unused")
+public class FileAdapter extends RecyclerView.Adapter {
+ private final Context context;
+ public static final String DIR_ROOT = ".";
+ public static final String DIR_PARENT = "..";
+ private final List data = new ArrayList<>();
+ private File rootDir = null;
+ private File currentFile = null;
+ private String[] allowExtensions = null;
+ private boolean onlyListDir = false;
+ private boolean showHomeDir = true;
+ private boolean showUpDir = true;
+ private boolean showHideDir = true;
+ private int fileSort = FileSort.BY_NAME_ASC;
+ private int itemHeight = 40;
+ private Drawable homeIcon;
+ private Drawable upIcon;
+ private Drawable folderIcon;
+ private Drawable fileIcon;
+ private OnPathClickedListener onPathClickedListener;
+
+ public FileAdapter(@NonNull Context context) {
+ this.context = context;
+ homeIcon = ContextCompat.getDrawable(context, R.mipmap.file_picker_home);
+ upIcon = ContextCompat.getDrawable(context, R.mipmap.file_picker_up);
+ folderIcon = ContextCompat.getDrawable(context, R.mipmap.file_picker_folder);
+ fileIcon = ContextCompat.getDrawable(context, R.mipmap.file_picker_file);
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ LinearLayout layout = new LinearLayout(context);
+ layout.setBackground(new StateDrawable(Color.WHITE, Color.LTGRAY));
+ layout.setOrientation(LinearLayout.HORIZONTAL);
+ layout.setGravity(Gravity.CENTER_VERTICAL);
+ int height = (int) (itemHeight * context.getResources().getDisplayMetrics().density);
+ int matchParent = ViewGroup.LayoutParams.MATCH_PARENT;
+ layout.setLayoutParams(new ViewGroup.LayoutParams(matchParent, height));
+ int padding = (int) (5 * context.getResources().getDisplayMetrics().density);
+ layout.setPadding(padding, padding, padding, padding);
+ ImageView imageView = new ImageView(context);
+ int wh = (int) (30 * context.getResources().getDisplayMetrics().density);
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(wh, wh));
+ imageView.setImageResource(android.R.drawable.ic_menu_report_image);
+ layout.addView(imageView);
+ TextView textView = new TextView(context);
+ LinearLayout.LayoutParams tvParams = new LinearLayout.LayoutParams(matchParent, matchParent);
+ tvParams.leftMargin = (int) (10 * context.getResources().getDisplayMetrics().density);
+ textView.setLayoutParams(tvParams);
+ textView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
+ textView.setSingleLine();
+ layout.addView(textView);
+ ViewHolder viewHolder = new ViewHolder(layout);
+ viewHolder.textView = textView;
+ viewHolder.imageView = imageView;
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
+ final FileEntity item = getItem(position);
+ holder.imageView.setImageDrawable(item.getIcon());
+ holder.textView.setText(item.getName());
+ if (onPathClickedListener == null) {
+ return;
+ }
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onPathClickedListener.onPathClicked(position, item.getFile().getAbsolutePath());
+ }
+ });
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getItemCount() {
+ return data.size();
+ }
+
+ public void setItemHeight(@Dimension(unit = Dimension.DP) int itemHeight) {
+ this.itemHeight = itemHeight;
+ notifyDataSetChanged();
+ }
+
+ public void setFileIcon(Drawable fileIcon) {
+ if (fileIcon == null) {
+ return;
+ }
+ this.fileIcon = fileIcon;
+ notifyDataSetChanged();
+ }
+
+ public void setFolderIcon(Drawable folderIcon) {
+ if (folderIcon == null) {
+ return;
+ }
+ this.folderIcon = folderIcon;
+ notifyDataSetChanged();
+ }
+
+ public void setHomeIcon(Drawable homeIcon) {
+ if (homeIcon == null) {
+ return;
+ }
+ this.homeIcon = homeIcon;
+ notifyDataSetChanged();
+ }
+
+ public void setUpIcon(Drawable upIcon) {
+ if (upIcon == null) {
+ return;
+ }
+ this.upIcon = upIcon;
+ notifyDataSetChanged();
+ }
+
+ /**
+ * 允许的扩展名
+ */
+ public void setAllowExtensions(String[] allowExtensions) {
+ if (this.allowExtensions != null && Arrays.equals(this.allowExtensions, allowExtensions)) {
+ return;
+ }
+ this.allowExtensions = allowExtensions;
+ loadData(getCurrentFile());
+ }
+
+ /**
+ * 是否仅仅读取目录
+ */
+ public void setOnlyListDir(boolean onlyListDir) {
+ if (this.onlyListDir == onlyListDir) {
+ return;
+ }
+ this.onlyListDir = onlyListDir;
+ loadData(getCurrentFile());
+ }
+
+ public boolean isOnlyListDir() {
+ return onlyListDir;
+ }
+
+ /**
+ * 是否显示返回主目录
+ */
+ public void setShowHomeDir(boolean showHomeDir) {
+ if (this.showHomeDir == showHomeDir) {
+ return;
+ }
+ this.showHomeDir = showHomeDir;
+ loadData(getCurrentFile());
+ }
+
+ public boolean isShowHomeDir() {
+ return showHomeDir;
+ }
+
+ /**
+ * 是否显示返回上一级
+ */
+ public void setShowUpDir(boolean showUpDir) {
+ if (this.showUpDir == showUpDir) {
+ return;
+ }
+ this.showUpDir = showUpDir;
+ loadData(getCurrentFile());
+ }
+
+ public boolean isShowUpDir() {
+ return showUpDir;
+ }
+
+ /**
+ * 是否显示隐藏的目录(以“.”开头)
+ */
+ public void setShowHideDir(boolean showHideDir) {
+ if (this.showHideDir == showHideDir) {
+ return;
+ }
+ this.showHideDir = showHideDir;
+ loadData(getCurrentFile());
+ }
+
+ public boolean isShowHideDir() {
+ return showHideDir;
+ }
+
+ @FileSort
+ public int getFileSort() {
+ return fileSort;
+ }
+
+ public void setFileSort(@FileSort int fileSort) {
+ if (this.fileSort == fileSort) {
+ return;
+ }
+ this.fileSort = fileSort;
+ loadData(getCurrentFile());
+ }
+
+ public File getCurrentFile() {
+ return currentFile;
+ }
+
+ public void loadData(File dir) {
+ if (dir == null) {
+ PickerLog.print("current directory is null");
+ return;
+ }
+ List entities = new ArrayList<>();
+ if (rootDir == null) {
+ rootDir = dir;
+ }
+ PickerLog.print("current directory path: " + dir);
+ currentFile = dir;
+ if (showHomeDir) {
+ //添加“返回主目录”
+ FileEntity root = new FileEntity();
+ root.setIcon(homeIcon);
+ root.setName(DIR_ROOT);
+ root.setFile(rootDir);
+ entities.add(root);
+ }
+ if (showUpDir && !File.separator.equals(dir.getAbsolutePath())) {
+ //添加“返回上一级目录”
+ FileEntity parent = new FileEntity();
+ parent.setIcon(upIcon);
+ parent.setName(DIR_PARENT);
+ parent.setFile(dir.getParentFile());
+ entities.add(parent);
+ }
+ List files = listFiles(currentFile, new SimpleFilter(onlyListDir, allowExtensions));
+ sortFiles(files, fileSort);
+ for (File file : files) {
+ if (!showHideDir && file.getName().startsWith(".")) {
+ continue;
+ }
+ FileEntity FileEntity = new FileEntity();
+ if (file.isDirectory()) {
+ FileEntity.setIcon(folderIcon);
+ } else {
+ FileEntity.setIcon(fileIcon);
+ }
+ FileEntity.setName(file.getName());
+ FileEntity.setFile(file);
+ entities.add(FileEntity);
+ }
+ data.clear();
+ data.addAll(entities);
+ notifyDataSetChanged();
+ }
+
+ public final void recycleData() {
+ data.clear();
+ if (homeIcon instanceof BitmapDrawable) {
+ Bitmap homeBitmap = ((BitmapDrawable) homeIcon).getBitmap();
+ if (null != homeBitmap && !homeBitmap.isRecycled()) {
+ homeBitmap.recycle();
+ }
+ }
+ if (upIcon instanceof BitmapDrawable) {
+ Bitmap upBitmap = ((BitmapDrawable) upIcon).getBitmap();
+ if (null != upBitmap && !upBitmap.isRecycled()) {
+ upBitmap.recycle();
+ }
+ }
+ if (folderIcon instanceof BitmapDrawable) {
+ Bitmap folderBitmap = ((BitmapDrawable) folderIcon).getBitmap();
+ if (null != folderBitmap && !folderBitmap.isRecycled()) {
+ folderBitmap.recycle();
+ }
+ }
+ if (fileIcon instanceof BitmapDrawable) {
+ Bitmap fileBitmap = ((BitmapDrawable) fileIcon).getBitmap();
+ if (null != fileBitmap && !fileBitmap.isRecycled()) {
+ fileBitmap.recycle();
+ }
+ }
+ }
+
+ public void setOnPathClickedListener(OnPathClickedListener listener) {
+ onPathClickedListener = listener;
+ }
+
+ public FileEntity getItem(int position) {
+ return data.get(position);
+ }
+
+ /**
+ * 列出指定目录下的所有子目录
+ */
+ private List listFiles(File startDir, FileFilter fileFilter) {
+ PickerLog.print(String.format("list dir %s", startDir));
+ if (!startDir.isDirectory()) {
+ return new ArrayList<>();
+ }
+ File[] dirs = startDir.listFiles(fileFilter);
+ if (dirs == null) {
+ return new ArrayList<>();
+ }
+ return Arrays.asList(dirs);
+ }
+
+ private void sortFiles(List files, @FileSort int sort) {
+ switch (sort) {
+ case FileSort.BY_NAME_ASC:
+ Collections.sort(files, new SortByName());
+ break;
+ case FileSort.BY_NAME_DESC:
+ Collections.sort(files, new SortByName());
+ Collections.reverse(files);
+ break;
+ case FileSort.BY_TIME_ASC:
+ Collections.sort(files, new SortByTime());
+ break;
+ case FileSort.BY_TIME_DESC:
+ Collections.sort(files, new SortByTime());
+ Collections.reverse(files);
+ break;
+ case FileSort.BY_SIZE_ASC:
+ Collections.sort(files, new SortBySize());
+ break;
+ case FileSort.BY_SIZE_DESC:
+ Collections.sort(files, new SortBySize());
+ Collections.reverse(files);
+ break;
+ case FileSort.BY_EXTENSION_ASC:
+ Collections.sort(files, new SortByExtension());
+ break;
+ case FileSort.BY_EXTENSION_DESC:
+ Collections.sort(files, new SortByExtension());
+ Collections.reverse(files);
+ break;
+ }
+ }
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/FileEntity.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/FileEntity.java
new file mode 100644
index 00000000..9c824152
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/FileEntity.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.adapter;
+
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.NonNull;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * 文件项信息
+ *
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2014/05/23 18:02
+ */
+public class FileEntity implements Serializable {
+ private Drawable icon;
+ private String name;
+ private File file;
+
+ public void setIcon(Drawable icon) {
+ this.icon = icon;
+ }
+
+ public Drawable getIcon() {
+ return icon;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public File getFile() {
+ return file;
+ }
+
+ public void setFile(File file) {
+ this.file = file;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "FileEntity{" +
+ "name='" + name + '\'' +
+ ", file='" + file + '\'' +
+ '}';
+ }
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/PathAdapter.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/PathAdapter.java
new file mode 100644
index 00000000..42caaf5c
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/PathAdapter.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.adapter;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.github.gzuliyujiang.filepicker.R;
+import com.github.gzuliyujiang.filepicker.contract.OnPathClickedListener;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.LinkedList;
+
+/**
+ * 文件路径数据适配
+ *
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2017/01/08 01:20
+ */
+@SuppressWarnings("unused")
+public class PathAdapter extends RecyclerView.Adapter {
+ private static final String ROOT_HINT = "ROOT";
+ private final Context context;
+ private final LinkedList paths = new LinkedList<>();
+ private Drawable arrowIcon = null;
+ private OnPathClickedListener onPathClickedListener;
+
+ public PathAdapter(@NonNull Context context) {
+ this.context = context;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ int wrapContent = ViewGroup.LayoutParams.WRAP_CONTENT;
+ LinearLayout layout = new LinearLayout(context);
+ layout.setOrientation(LinearLayout.HORIZONTAL);
+ layout.setGravity(Gravity.CENTER_VERTICAL);
+ layout.setLayoutParams(new ViewGroup.LayoutParams(wrapContent, wrapContent));
+ TextView textView = new TextView(context);
+ LinearLayout.LayoutParams tvParams = new LinearLayout.LayoutParams(wrapContent, wrapContent);
+ textView.setLayoutParams(tvParams);
+ textView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
+ int padding = (int) (5 * context.getResources().getDisplayMetrics().density);
+ textView.setPadding(padding, 0, padding, 0);
+ layout.addView(textView);
+ ImageView imageView = new ImageView(context);
+ int width = (int) (20 * context.getResources().getDisplayMetrics().density);
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(width, wrapContent));
+ layout.addView(imageView);
+ ViewHolder viewHolder = new ViewHolder(layout);
+ viewHolder.textView = textView;
+ viewHolder.imageView = imageView;
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
+ holder.textView.setText(paths.get(position));
+ holder.imageView.setImageDrawable(arrowIcon);
+ if (onPathClickedListener == null) {
+ return;
+ }
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onPathClickedListener.onPathClicked(position, getPath(position));
+ }
+ });
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getItemCount() {
+ return paths.size();
+ }
+
+ public void setArrowIcon(Drawable arrowIcon) {
+ this.arrowIcon = arrowIcon;
+ }
+
+ public void updatePath(File file) {
+ if (arrowIcon == null) {
+ arrowIcon = ContextCompat.getDrawable(context, R.mipmap.file_picker_arrow);
+ }
+ paths.clear();
+ String path = file.getAbsolutePath();
+ if (!File.separator.equals(path)) {
+ Collections.addAll(paths, path.substring(path.indexOf(File.separator) + 1)
+ .split(File.separator));
+ }
+ paths.addFirst(ROOT_HINT);
+ notifyDataSetChanged();
+ }
+
+ public String getPath(int position) {
+ StringBuilder sb = new StringBuilder(File.separator);
+ //忽略根目录
+ if (position == 0) {
+ return sb.toString();
+ }
+ for (int i = 1; i <= position; i++) {
+ sb.append(paths.get(i)).append(File.separator);
+ }
+ return sb.toString();
+ }
+
+ public final void recycleData() {
+ paths.clear();
+ if (arrowIcon instanceof BitmapDrawable) {
+ Bitmap homeBitmap = ((BitmapDrawable) arrowIcon).getBitmap();
+ if (null != homeBitmap && !homeBitmap.isRecycled()) {
+ homeBitmap.recycle();
+ }
+ }
+ }
+
+ public void setOnPathClickedListener(OnPathClickedListener listener) {
+ onPathClickedListener = listener;
+ }
+
+}
diff --git a/basepicker/src/main/java/com/github/gzuliyujiang/basepicker/StateDrawable.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/StateDrawable.java
similarity index 89%
rename from basepicker/src/main/java/com/github/gzuliyujiang/basepicker/StateDrawable.java
rename to FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/StateDrawable.java
index de39b40c..549a8578 100644
--- a/basepicker/src/main/java/com/github/gzuliyujiang/basepicker/StateDrawable.java
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/StateDrawable.java
@@ -11,7 +11,7 @@
* See the Mulan PSL v2 for more details.
*/
-package com.github.gzuliyujiang.basepicker;
+package com.github.gzuliyujiang.filepicker.adapter;
import android.content.Context;
import android.graphics.Color;
@@ -22,6 +22,7 @@
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
/**
* 按下状态与普通状态下显示不同的颜色
@@ -30,7 +31,7 @@
* @since 2017/01/01 05:30
*/
@SuppressWarnings("unused")
-public class StateDrawable extends StateListDrawable {
+class StateDrawable extends StateListDrawable {
public StateDrawable(@ColorInt int pressedColor) {
this(Color.TRANSPARENT, pressedColor);
@@ -41,7 +42,7 @@ public StateDrawable(@ColorInt int normalColor, @ColorInt int pressedColor) {
}
public StateDrawable(@NonNull Context context, @DrawableRes int normalRes, @DrawableRes int pressedRes) {
- addState(context.getDrawable(normalRes), context.getDrawable(pressedRes));
+ addState(ContextCompat.getDrawable(context, normalRes), ContextCompat.getDrawable(context, pressedRes));
}
public StateDrawable(Drawable normal, Drawable pressed) {
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/ViewHolder.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/ViewHolder.java
new file mode 100644
index 00000000..c38fe316
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/adapter/ViewHolder.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.adapter;
+
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2021/6/10 20:47
+ */
+public class ViewHolder extends RecyclerView.ViewHolder {
+ public TextView textView;
+ public ImageView imageView;
+
+ public ViewHolder(@NonNull View itemView) {
+ super(itemView);
+ }
+
+}
\ No newline at end of file
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/annotation/ExplorerMode.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/annotation/ExplorerMode.java
new file mode 100644
index 00000000..5ad7d281
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/annotation/ExplorerMode.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2021/6/10 15:28
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface ExplorerMode {
+ int DIRECTORY = 0;
+ int FILE = 1;
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/annotation/FileSort.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/annotation/FileSort.java
new file mode 100644
index 00000000..4872db3f
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/annotation/FileSort.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2021/6/10 14:35
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface FileSort {
+ int BY_NAME_ASC = 0;
+ int BY_NAME_DESC = 1;
+ int BY_TIME_ASC = 2;
+ int BY_TIME_DESC = 3;
+ int BY_SIZE_ASC = 4;
+ int BY_SIZE_DESC = 5;
+ int BY_EXTENSION_ASC = 6;
+ int BY_EXTENSION_DESC = 7;
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/contract/OnFileClickedListener.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/contract/OnFileClickedListener.java
new file mode 100644
index 00000000..e367071d
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/contract/OnFileClickedListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.contract;
+
+import androidx.annotation.NonNull;
+
+import java.io.File;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2021/6/10 19:33
+ */
+public interface OnFileClickedListener {
+
+ void onFileClicked(@NonNull File file);
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/contract/OnFilePickedListener.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/contract/OnFilePickedListener.java
new file mode 100644
index 00000000..b5c2160f
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/contract/OnFilePickedListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.contract;
+
+import androidx.annotation.NonNull;
+
+import java.io.File;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2015/9/29
+ */
+public interface OnFilePickedListener {
+
+ void onFilePicked(@NonNull File file);
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/contract/OnPathClickedListener.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/contract/OnPathClickedListener.java
new file mode 100644
index 00000000..f6cb6911
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/contract/OnPathClickedListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.contract;
+
+import androidx.annotation.NonNull;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2021/6/10 20:15
+ */
+public interface OnPathClickedListener {
+
+ void onPathClicked(int position, @NonNull String path);
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/filter/PatternFilter.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/filter/PatternFilter.java
new file mode 100644
index 00000000..9a4227e1
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/filter/PatternFilter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.filter;
+
+import androidx.annotation.NonNull;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.regex.Pattern;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2021/6/10 15:07
+ */
+public class PatternFilter implements FileFilter {
+ private final Pattern pattern;
+
+ public PatternFilter(@NonNull Pattern pattern) {
+ this.pattern = pattern;
+ }
+
+ @Override
+ public boolean accept(File pathname) {
+ if (pathname == null) {
+ return false;
+ }
+ return pattern.matcher(pathname.getName()).find();
+ }
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/filter/SimpleFilter.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/filter/SimpleFilter.java
new file mode 100644
index 00000000..72da6e3b
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/filter/SimpleFilter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.filter;
+
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Arrays;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2021/6/10 15:11
+ */
+public class SimpleFilter implements FileFilter {
+ private final boolean isOnlyDir;
+ private final String[] allowExtensions;
+
+ public SimpleFilter(boolean isOnlyDir, @Nullable String[] allowExtensions) {
+ this.isOnlyDir = isOnlyDir;
+ this.allowExtensions = allowExtensions;
+ }
+
+ @Override
+ public boolean accept(File pathname) {
+ if (pathname == null) {
+ return false;
+ }
+ if (isOnlyDir && pathname.isFile()) {
+ return false;
+ }
+ if (allowExtensions == null || allowExtensions.length == 0) {
+ return true;
+ }
+ //返回当前目录所有以某些扩展名结尾的文件
+ String extension = getExtension(pathname.getPath());
+ return Arrays.toString(allowExtensions).contains(extension);
+ }
+
+ private String getExtension(String path) {
+ if (path == null) {
+ return "";
+ }
+ int slashPos = path.lastIndexOf(File.separator);
+ if (slashPos != -1) {
+ path = path.substring(slashPos);
+ }
+ int dotPos = path.lastIndexOf('.');
+ if (0 <= dotPos) {
+ return path.substring(dotPos + 1);
+ } else {
+ return "";
+ }
+ }
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortByExtension.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortByExtension.java
new file mode 100644
index 00000000..01bb5fba
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortByExtension.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.sort;
+
+import java.io.File;
+import java.util.Comparator;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2014/4/18
+ */
+public class SortByExtension implements Comparator {
+
+ @Override
+ public int compare(File f1, File f2) {
+ if (f1 == null || f2 == null) {
+ if (f1 == null) {
+ return -1;
+ } else {
+ return 1;
+ }
+ } else {
+ if (f1.isDirectory() && f2.isFile()) {
+ return -1;
+ } else if (f1.isFile() && f2.isDirectory()) {
+ return 1;
+ } else {
+ return f1.getName().compareToIgnoreCase(f2.getName());
+ }
+ }
+ }
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortByName.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortByName.java
new file mode 100644
index 00000000..09d194cb
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortByName.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.sort;
+
+import java.io.File;
+import java.util.Comparator;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2014/4/18
+ */
+public class SortByName implements Comparator {
+ private static final boolean CASE_SENSITIVE = false;
+
+ @Override
+ public int compare(File f1, File f2) {
+ if (f1 == null || f2 == null) {
+ if (f1 == null) {
+ return -1;
+ } else {
+ return 1;
+ }
+ } else {
+ if (f1.isDirectory() && f2.isFile()) {
+ return -1;
+ } else if (f1.isFile() && f2.isDirectory()) {
+ return 1;
+ } else {
+ String s1 = f1.getName();
+ String s2 = f2.getName();
+ if (CASE_SENSITIVE) {
+ return s1.compareTo(s2);
+ } else {
+ return s1.compareToIgnoreCase(s2);
+ }
+ }
+ }
+ }
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortBySize.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortBySize.java
new file mode 100644
index 00000000..832ce3e1
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortBySize.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.sort;
+
+import java.io.File;
+import java.util.Comparator;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2014/4/18
+ */
+public class SortBySize implements Comparator {
+
+ @Override
+ public int compare(File f1, File f2) {
+ if (f1 == null || f2 == null) {
+ if (f1 == null) {
+ return -1;
+ } else {
+ return 1;
+ }
+ } else {
+ if (f1.isDirectory() && f2.isFile()) {
+ return -1;
+ } else if (f1.isFile() && f2.isDirectory()) {
+ return 1;
+ } else {
+ if (f1.length() < f2.length()) {
+ return -1;
+ } else if (f1.length() > f2.length()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ }
+
+}
diff --git a/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortByTime.java b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortByTime.java
new file mode 100644
index 00000000..22dfdbe1
--- /dev/null
+++ b/FilePicker/src/main/java/com/github/gzuliyujiang/filepicker/sort/SortByTime.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.filepicker.sort;
+
+import java.io.File;
+import java.util.Comparator;
+
+/**
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2014/4/18
+ */
+public class SortByTime implements Comparator {
+
+ @Override
+ public int compare(File f1, File f2) {
+ if (f1 == null || f2 == null) {
+ if (f1 == null) {
+ return -1;
+ } else {
+ return 1;
+ }
+ } else {
+ if (f1.isDirectory() && f2.isFile()) {
+ return -1;
+ } else if (f1.isFile() && f2.isDirectory()) {
+ return 1;
+ } else {
+ if (f1.lastModified() > f2.lastModified()) {
+ return -1;
+ } else if (f1.lastModified() < f2.lastModified()) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ }
+
+}
diff --git a/FilePicker/src/main/res/layout/file_picker_content.xml b/FilePicker/src/main/res/layout/file_picker_content.xml
new file mode 100644
index 00000000..bc63e567
--- /dev/null
+++ b/FilePicker/src/main/res/layout/file_picker_content.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FilePicker/src/main/res/mipmap-xxhdpi/file_picker_arrow.png b/FilePicker/src/main/res/mipmap-xxhdpi/file_picker_arrow.png
new file mode 100644
index 0000000000000000000000000000000000000000..0208fb401b605f7268fae8c265f62fec879291df
GIT binary patch
literal 425
zcmV;a0apHrP)Y5ix~^yE+y}Y>+j0QIc4j)Aj!PiSfKrG~KwIkXLJ33=P#R$Zlth>T
zr4YRUB@n#d29Ob8(m8k5^`3{;
z%x^){G;38=y?IxDQ`hxzs35d%0*J^8fOjwKo|*ST;g1>zw5l3;--R=a#IYedc}u(Q)7*
TZu9%~00000NkvXXu0mjf%{8yd
literal 0
HcmV?d00001
diff --git a/FilePicker/src/main/res/mipmap-xxhdpi/file_picker_file.png b/FilePicker/src/main/res/mipmap-xxhdpi/file_picker_file.png
new file mode 100644
index 0000000000000000000000000000000000000000..fbcd6d8104853cab7ec5f4105f8e864fb4c9115c
GIT binary patch
literal 489
zcmVeMYOeY>The_`i9GLfEHt&=7|J(n
zkI_Esg^4>}cBEyeFD$vWsz3fl6Nh@s$r!fBXlMV|46@&QsKQq3^9@_AKRA&OtOHXj
f{%#cdf2scfK*F;ilScM+00000NkvXXu0mjf>c86s
literal 0
HcmV?d00001
diff --git a/FilePicker/src/main/res/mipmap-xxhdpi/file_picker_folder.png b/FilePicker/src/main/res/mipmap-xxhdpi/file_picker_folder.png
new file mode 100644
index 0000000000000000000000000000000000000000..a990053d9e2c418c348d01fd5937230b1d5fc302
GIT binary patch
literal 736
zcmV<60w4W}P)PqPc821<&wCd;M7Y3jwO@D7
z%+BoWoij7+PT3CGd;A{1$L~gy@#)3fCC8t>yPaY+hF{Fxlk=|tW?lin=f(sHCVY81
zdGTKU9zVGGi@AGr@(C+F(t~2elyOk5e|~&+d3w#xZ6cSf`HQ)Gd~yNcXf`W{d~V7B
zK;DEQ0-yi*{;`;ptcg+3jJ4?&?N=A8B!19?qX!SWeX3vS01yFq`SuwA#89s?=|m*1
zmPO;k@x3?Ses6&X;PeWP+9MY7#suz8k?&X#0{|C@nIp5rbPA8g&k^vm>1sf+|An|T
z#p=Pr0;#$p4v?!~#Hw}!d|Mg-xWlOh?oUPSk5nPx+!Je%!Be_f+0in(>+JG49AuEN
z_E=?1j6+ky-W#>SEmJBB4kzaG{_L89n8@1pT#xS=Q~RAw0l?<~TsAdbi^Vod<3$~c
zJny}|Iv63I2Rjdew$kFd3Dx@T-P%12Fp$w&*~6^
z#h3{E2)lN@>vvo)i{p5yZ_J@5H)E@sGBa;q^-Ki9%>Alo5?k$NZ=Cma6F>|EAos!h
zv6M{XgJuATa`p_slK`OHCl>?gS6|_x)U1}P2%$NPhq>zoB9j
S=?~BV0000kdg00002VoOIv0RM-N
z%)bBt010qNS#tmYE+YT{E+YYWr9XB6000McNliru;s_cCBqqKlp3MLN04L6%PoLN~lk31Q16>sf#Y|(_F`T8H_Nw
zJFY%Pp-|{iDszPVaMpeoUwFuiXE>!V675%MRo%;UhaH7R+tidgf6I^v#KFu1F1$RZ
zzKK=RJM?m8fOSpH?@%PM$ZEPQ7DNHWzVT&6e5Ia8?^!85v9d#UvF3S)2OS$ZHm-?@
z0|2qtex!(RH1N=aQ+_Z?Z}~>8dm8wLB9mzjsze%?LF}=g*ny8%-1DjxvT?ZD^M6Wh
z=qRw8%%syJ{0q}An}s{+)MhQfTWY`StL2k;C%}(>S@h5e6DHh0#|DJGo#i9_8^M3+
aas3TuN*lrX9A_B-0000h$TNrmK^MV|s9R6sBkA`n3Y(rdGl?&lf6;Uj^M75{U1ba@CHkat
zA9ep{0N?k0-+!1Ov&nVcFbr3#m1S8OUjZ3eHrew$&+|Cv7-LH5@pzn0rx^oCPm$}o
zfa5r>>rzUiD2k$pF@_KlHyMT@rL5QMy!{{Z}Oifr44J%oh7M2&zUgkX#@#&C1JUeD+AOaR-qJDm;~F~;)w
zJVMAcO$C6kE|<&odIdaav)KeekOHtQtJ!SAX@NV%ViDX`P8eh7^Z9f-5ke3`>UGmJ
z!!TSfmkEHWUq~r~Ab^#sCAZsc{gMz8$1&%8x7(?k`s6RbY&J_g9t;LT2t7Yg2XK}a
zi^Y$ozXI+B?qCfVC}#c9O8_aQ5JEprDuRsvROt~YYn3IKzb66(0AF;=66g&8DP;oi
zsT4tFsN{gE)hha4=P=oBw~ql5JpfC<
xy3#*eDwT?Er2GC>Ylp+(0l%)@>5#j&<002ovPDHLkV1jc19IXHV
literal 0
HcmV?d00001
diff --git a/README.md b/README.md
index f180179c..22747ea3 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ allprojects {
```groovy
dependencies {
- implementation 'com.github.gzu-liyujiang:AndroidPicker:basepicker:'
+ implementation 'com.github.gzu-liyujiang:AndroidPicker:BasePicker:'
}
```
@@ -37,7 +37,7 @@ dependencies {
```groovy
dependencies {
- implementation 'com.github.gzu-liyujiang:AndroidPicker:wheelview:'
+ implementation 'com.github.gzu-liyujiang:AndroidPicker:WheelView:'
}
```
@@ -45,7 +45,7 @@ dependencies {
```groovy
dependencies {
- implementation 'com.github.gzu-liyujiang:AndroidPicker:wheelpicker:'
+ implementation 'com.github.gzu-liyujiang:AndroidPicker:WheelPicker:'
}
```
@@ -53,7 +53,7 @@ dependencies {
```groovy
dependencies {
- implementation 'com.github.gzu-liyujiang:AndroidPicker:filepicker:'
+ implementation 'com.github.gzu-liyujiang:AndroidPicker:FilePicker:'
}
```
@@ -61,7 +61,7 @@ dependencies {
```groovy
dependencies {
- implementation 'com.github.gzu-liyujiang:AndroidPicker:colorpicker:'
+ implementation 'com.github.gzu-liyujiang:AndroidPicker:ColorPicker:'
}
```
@@ -69,7 +69,7 @@ dependencies {
```groovy
dependencies {
- implementation 'com.github.gzu-liyujiang:AndroidPicker:calendarpicker:'
+ implementation 'com.github.gzu-liyujiang:AndroidPicker:CalendarPicker:'
}
```
diff --git a/WheelPicker/src/main/AndroidManifest.xml b/WheelPicker/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..2ac2b188
--- /dev/null
+++ b/WheelPicker/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+
+
+
diff --git a/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/BirthdayPicker.java b/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/BirthdayPicker.java
index 33deec4c..a04fbdd1 100644
--- a/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/BirthdayPicker.java
+++ b/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/BirthdayPicker.java
@@ -31,6 +31,7 @@
* @since 2019/5/14 14:31
* @since 2.0
*/
+@SuppressWarnings("unused")
public class BirthdayPicker extends DatePicker {
private static final int MAX_AGE = 100;
diff --git a/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/NumberPicker.java b/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/NumberPicker.java
index fa7d01a1..4e6463d8 100644
--- a/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/NumberPicker.java
+++ b/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/NumberPicker.java
@@ -32,6 +32,7 @@
* @author 李玉江[QQ:1032694760]
* @since 2015/10/24
*/
+@SuppressWarnings("unused")
public class NumberPicker extends ConfirmPicker {
protected NumberWheelLayout wheelLayout;
private OnNumberPickedListener onNumberPickedListener;
diff --git a/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/OptionPicker.java b/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/OptionPicker.java
index bce1fd37..10830f5e 100644
--- a/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/OptionPicker.java
+++ b/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/OptionPicker.java
@@ -32,7 +32,7 @@
* 单项选择器
*
* @author 贵州山野羡民(1032694760@qq.com)
- * @see TextProvider
+ * @see com.github.gzuliyujiang.wheelview.contract.TextProvider
* @since 2019/5/8 10:04
*/
@SuppressWarnings({"unused"})
diff --git a/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/PhoneCodePicker.java b/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/PhoneCodePicker.java
index 7a39a073..1c538483 100644
--- a/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/PhoneCodePicker.java
+++ b/WheelPicker/src/main/java/com/github/gzuliyujiang/wheelpicker/PhoneCodePicker.java
@@ -29,6 +29,7 @@
* @author 贵州山野羡民(1032694760@qq.com)
* @since 2019/5/10 16:44
*/
+@SuppressWarnings("unused")
public class PhoneCodePicker extends OptionPicker {
public PhoneCodePicker(@NonNull Activity activity) {
diff --git a/WheelView/README.md b/WheelView/README.md
index cc83c535..a3867187 100644
--- a/WheelView/README.md
+++ b/WheelView/README.md
@@ -1,3 +1,3 @@
-# 滚轮控件
+# 滚轮
仿 IOS 风格的滚轮控件。
diff --git a/WheelView/consumer-rules.pro b/WheelView/consumer-rules.pro
new file mode 100644
index 00000000..057cd949
--- /dev/null
+++ b/WheelView/consumer-rules.pro
@@ -0,0 +1 @@
+# 本库模块专用的混淆规则
diff --git a/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/annotation/ItemTextAlign.java b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/annotation/ItemTextAlign.java
new file mode 100644
index 00000000..d69a4f6d
--- /dev/null
+++ b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/annotation/ItemTextAlign.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.wheelview.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * 滚轮条目文本对齐方式
+ *
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2019/6/19 12:04
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface ItemTextAlign {
+ int CENTER = 0;
+ int LEFT = 1;
+ int RIGHT = 2;
+}
diff --git a/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/contract/OnWheelChangedListener.java b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/contract/OnWheelChangedListener.java
new file mode 100644
index 00000000..1f13a54e
--- /dev/null
+++ b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/contract/OnWheelChangedListener.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.wheelview.contract;
+
+import com.github.gzuliyujiang.wheelview.widget.WheelView;
+
+/**
+ * 滚轮滑动接口
+ *
+ * @author Florent Champigny
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2019/5/14 20:03
+ */
+public interface OnWheelChangedListener {
+
+ /**
+ * Invoke when scroll stopped
+ * Will return a distance offset which between current scroll position and
+ * initial position, this offset is a positive or a negative, positive means
+ * scrolling from bottom to top, negative means scrolling from top to bottom
+ *
+ * @param view wheel view
+ * @param offset Distance offset which between current scroll position and initial position
+ */
+ void onWheelScrolled(WheelView view, int offset);
+
+ /**
+ * Invoke when scroll stopped
+ * This method will be called when wheel stop and return current selected item data's
+ * position in list
+ *
+ * @param view wheel view
+ * @param position Current selected item data's position in list
+ */
+ void onWheelSelected(WheelView view, int position);
+
+ /**
+ * Invoke when scroll state changed
+ * The state always between idle, dragging, and scrolling, this method will
+ * be called when they switch
+ *
+ * @param view wheel view
+ * @param state {@link WheelView#SCROLL_STATE_IDLE}
+ * {@link WheelView#SCROLL_STATE_DRAGGING}
+ * {@link WheelView#SCROLL_STATE_SCROLLING}
+ *
+ * State only one of the following
+ * {@link WheelView#SCROLL_STATE_IDLE}
+ * Express WheelPicker in state of idle
+ * {@link WheelView#SCROLL_STATE_DRAGGING}
+ * Express WheelPicker in state of dragging
+ * {@link WheelView#SCROLL_STATE_SCROLLING}
+ * Express WheelPicker in state of scrolling
+ */
+ void onWheelScrollStateChanged(WheelView view, int state);
+
+ /**
+ * Invoke when loop finished
+ *
+ * @param view wheel view
+ */
+ void onWheelLoopFinished(WheelView view);
+
+}
diff --git a/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/contract/TextProvider.java b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/contract/TextProvider.java
new file mode 100644
index 00000000..55e686c4
--- /dev/null
+++ b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/contract/TextProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.wheelview.contract;
+
+/**
+ * 提供显示的文本
+ *
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2019/5/14 20:01
+ */
+public interface TextProvider {
+
+ /**
+ * 提供显示的文本
+ *
+ * @return 显示的文本
+ */
+ String provideText();
+
+}
+
diff --git a/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/contract/WheelFormatter.java b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/contract/WheelFormatter.java
new file mode 100644
index 00000000..ed51f0ec
--- /dev/null
+++ b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/contract/WheelFormatter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.wheelview.contract;
+
+import androidx.annotation.NonNull;
+
+/**
+ * 滚轮条目显示文本格式化接口
+ *
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2019/5/14 20:02
+ */
+public interface WheelFormatter {
+
+ /**
+ * 格式化滚轮条目显示文本
+ *
+ * @param item 滚轮条目的内容
+ * @return 格式化后最终显示的文本
+ */
+ String formatItem(@NonNull Object item);
+
+}
diff --git a/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/widget/NumberWheelView.java b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/widget/NumberWheelView.java
new file mode 100644
index 00000000..708fd703
--- /dev/null
+++ b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/widget/NumberWheelView.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.wheelview.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 数字滚轮控件
+ *
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @since 2019/5/13 19:13
+ */
+public class NumberWheelView extends WheelView {
+
+ public NumberWheelView(Context context) {
+ super(context);
+ }
+
+ public NumberWheelView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public NumberWheelView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected List> generatePreviewData() {
+ List data = new ArrayList<>();
+ for (int i = 1; i <= 10; i = i + 1) {
+ data.add(i);
+ }
+ return data;
+ }
+
+ @Deprecated
+ @Override
+ public void setData(List> data) {
+ throw new UnsupportedOperationException("Use setRange instead");
+ }
+
+ public void setRange(int min, int max, int step) {
+ int minValue = Math.min(min, max);
+ int maxValue = Math.max(min, max);
+ // 指定初始容量,避免OutOfMemory
+ int capacity = (maxValue - minValue) / step;
+ List data = new ArrayList<>(capacity);
+ for (int i = minValue; i <= maxValue; i = i + step) {
+ data.add(i);
+ }
+ super.setData(data);
+ }
+
+ public void setRange(float min, float max, float step) {
+ float minValue = Math.min(min, max);
+ float maxValue = Math.max(min, max);
+ // 指定初始容量,避免OutOfMemory
+ int capacity = (int) ((maxValue - minValue) / step);
+ List data = new ArrayList<>(capacity);
+ for (float i = minValue; i <= maxValue; i = i + step) {
+ data.add(i);
+ }
+ super.setData(data);
+ }
+
+}
diff --git a/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/widget/WheelView.java b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/widget/WheelView.java
new file mode 100644
index 00000000..048ba302
--- /dev/null
+++ b/WheelView/src/main/java/com/github/gzuliyujiang/wheelview/widget/WheelView.java
@@ -0,0 +1,1069 @@
+/*
+ * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com>
+ *
+ * The software is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package com.github.gzuliyujiang.wheelview.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Camera;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.Scroller;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Px;
+import androidx.annotation.StyleRes;
+
+import com.github.gzuliyujiang.wheelview.R;
+import com.github.gzuliyujiang.wheelview.annotation.ItemTextAlign;
+import com.github.gzuliyujiang.wheelview.contract.OnWheelChangedListener;
+import com.github.gzuliyujiang.wheelview.contract.TextProvider;
+import com.github.gzuliyujiang.wheelview.contract.WheelFormatter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 滚轮控件。Adapted from https://github.com/florent37/SingleDateAndTimePicker/.../WheelPicker.java
+ *
+ * @author 贵州山野羡民(1032694760@qq.com)
+ * @see TextProvider
+ * @see OnWheelChangedListener
+ * @since 2019/5/8 11:11
+ */
+@SuppressWarnings({"unused"})
+public class WheelView extends View implements Runnable {
+ public static final int SCROLL_STATE_IDLE = 0;
+ public static final int SCROLL_STATE_DRAGGING = 1;
+ public static final int SCROLL_STATE_SCROLLING = 2;
+
+ protected List> data = new ArrayList<>();
+ protected WheelFormatter formatter;
+ protected Object defaultItem;
+ protected int visibleItemCount;
+ protected int defaultItemPosition;
+ protected int currentPosition;
+ protected String maxWidthText;
+ protected int textColor, textColorSelected;
+ protected int textSize;
+ protected float indicatorSize;
+ protected int indicatorColor;
+ protected int curtainColor;
+ protected int itemSpace;
+ protected int textAlign;
+ protected boolean sameWidthEnabled;
+ protected boolean indicatorEnabled;
+ protected boolean curtainEnabled;
+ protected boolean atmosphericEnabled;
+ protected boolean cyclicEnabled;
+ protected boolean curvedEnabled;
+ protected int curvedMaxAngle = 90;
+
+ private final Handler handler = new Handler();
+ private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.LINEAR_TEXT_FLAG);
+ private final Scroller scroller;
+ private VelocityTracker tracker;
+ private OnWheelChangedListener onWheelChangedListener;
+ private final Rect rectDrawn = new Rect();
+ private final Rect rectIndicatorHead = new Rect();
+ private final Rect rectIndicatorFoot = new Rect();
+ private final Rect rectCurrentItem = new Rect();
+ private final Camera camera = new Camera();
+ private final Matrix matrixRotate = new Matrix();
+ private final Matrix matrixDepth = new Matrix();
+ private int lastScrollPosition;
+ private int drawnItemCount;
+ private int halfDrawnItemCount;
+ private int textMaxWidth, textMaxHeight;
+ private int itemHeight, halfItemHeight;
+ private int halfWheelHeight;
+ private int minFlingYCoordinate, maxFlingYCoordinate;
+ private int wheelCenterXCoordinate, wheelCenterYCoordinate;
+ private int drawnCenterXCoordinate, drawnCenterYCoordinate;
+ private int scrollOffsetYCoordinate;
+ private int lastPointYCoordinate;
+ private int downPointYCoordinate;
+ private final int minimumVelocity;
+ private final int maximumVelocity;
+ private final int touchSlop;
+ private boolean isClick;
+ private boolean isForceFinishScroll;
+ private final AttributeSet attrs;
+
+ public WheelView(Context context) {
+ this(context, null);
+ }
+
+ public WheelView(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.WheelStyle);
+ }
+
+ public WheelView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ this.attrs = attrs;
+ initAttrs(context, attrs, defStyleAttr, R.style.WheelDefault);
+ updateVisibleItemCount();
+ paint.setTextSize(textSize);
+ scroller = new Scroller(context);
+ ViewConfiguration configuration = ViewConfiguration.get(context);
+ minimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ maximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ touchSlop = configuration.getScaledTouchSlop();
+ if (isInEditMode()) {
+ setData(generatePreviewData());
+ }
+ }
+
+ public void setStyle(@StyleRes int style) {
+ if (attrs == null) {
+ throw new RuntimeException("Please use " + getClass().getSimpleName() + " in xml");
+ }
+ initAttrs(getContext(), attrs, R.attr.WheelStyle, style);
+ requestLayout();
+ invalidate();
+ }
+
+ private void initAttrs(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ if (attrs == null) {
+ textSize = (int) (15 * context.getResources().getDisplayMetrics().scaledDensity);
+ visibleItemCount = 5;
+ defaultItemPosition = 0;
+ sameWidthEnabled = false;
+ maxWidthText = "";
+ textColorSelected = 0xFF000000;
+ textColor = 0xFF888888;
+ itemSpace = (int) (20 * context.getResources().getDisplayMetrics().density);
+ cyclicEnabled = false;
+ indicatorEnabled = true;
+ indicatorColor = 0xFFC9C9C9;
+ indicatorSize = 1 * context.getResources().getDisplayMetrics().density;
+ curtainEnabled = false;
+ curtainColor = 0xFFFFFFFF;
+ atmosphericEnabled = false;
+ curvedEnabled = false;
+ curvedMaxAngle = 90;
+ textAlign = ItemTextAlign.CENTER;
+ return;
+ }
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WheelView,
+ defStyleAttr, defStyleRes);
+ onAttributeSet(context, typedArray);
+ typedArray.recycle();
+ }
+
+ protected void onAttributeSet(@NonNull Context context, @NonNull TypedArray typedArray) {
+ float density = context.getResources().getDisplayMetrics().density;
+ float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
+ textSize = typedArray.getDimensionPixelSize(R.styleable.WheelView_wheel_itemTextSize,
+ (int) (15 * scaledDensity));
+ visibleItemCount = typedArray.getInt(R.styleable.WheelView_wheel_visibleItemCount, 5);
+ sameWidthEnabled = typedArray.getBoolean(R.styleable.WheelView_wheel_sameWidthEnabled, false);
+ maxWidthText = typedArray.getString(R.styleable.WheelView_wheel_maxWidthText);
+ textColorSelected = typedArray.getColor(R.styleable.WheelView_wheel_itemTextColorSelected, 0xFF000000);
+ textColor = typedArray.getColor(R.styleable.WheelView_wheel_itemTextColor, 0xFF888888);
+ itemSpace = typedArray.getDimensionPixelSize(R.styleable.WheelView_wheel_itemSpace, (int) (20 * density));
+ cyclicEnabled = typedArray.getBoolean(R.styleable.WheelView_wheel_cyclicEnabled, false);
+ indicatorEnabled = typedArray.getBoolean(R.styleable.WheelView_wheel_indicatorEnabled, true);
+ indicatorColor = typedArray.getColor(R.styleable.WheelView_wheel_indicatorColor, 0xFFC9C9C9);
+ indicatorSize = typedArray.getDimension(R.styleable.WheelView_wheel_indicatorSize, 1 * density);
+ curtainEnabled = typedArray.getBoolean(R.styleable.WheelView_wheel_curtainEnabled, false);
+ curtainColor = typedArray.getColor(R.styleable.WheelView_wheel_curtainColor, 0xFFFFFFFF);
+ atmosphericEnabled = typedArray.getBoolean(R.styleable.WheelView_wheel_atmosphericEnabled, false);
+ curvedEnabled = typedArray.getBoolean(R.styleable.WheelView_wheel_curvedEnabled, false);
+ curvedMaxAngle = typedArray.getInteger(R.styleable.WheelView_wheel_curvedMaxAngle, 90);
+ textAlign = typedArray.getInt(R.styleable.WheelView_wheel_itemTextAlign, ItemTextAlign.CENTER);
+ }
+
+ protected List> generatePreviewData() {
+ List data = new ArrayList<>();
+ data.add("贵州穿青人");
+ data.add("大定府羡民");
+ data.add("不在五十六个民族之内");
+ data.add("已识别待定民族");
+ data.add("穿青山魈人马");
+ data.add("李裕江");
+ return data;
+ }
+
+ private void updateVisibleItemCount() {
+ final int minCount = 2;
+ if (visibleItemCount < minCount) {
+ throw new ArithmeticException("Visible item count can not be less than " + minCount);
+ }
+
+ int evenNumberFlag = 2;
+ if (visibleItemCount % evenNumberFlag == 0) {
+ visibleItemCount += 1;
+ }
+ drawnItemCount = visibleItemCount + 2;
+ halfDrawnItemCount = drawnItemCount / 2;
+ }
+
+ private void computeTextWidthAndHeight() {
+ textMaxWidth = textMaxHeight = 0;
+ if (sameWidthEnabled) {
+ textMaxWidth = (int) paint.measureText(formatItem(0));
+ } else if (!TextUtils.isEmpty(maxWidthText)) {
+ textMaxWidth = (int) paint.measureText(maxWidthText);
+ } else {
+ // 未指定最宽的文本,须遍历测量查找最宽的作为基准
+ int itemCount = getItemCount();
+ for (int i = 0; i < itemCount; ++i) {
+ int width = (int) paint.measureText(formatItem(i));
+ textMaxWidth = Math.max(textMaxWidth, width);
+ }
+ }
+ Paint.FontMetrics metrics = paint.getFontMetrics();
+ textMaxHeight = (int) (metrics.bottom - metrics.top);
+ }
+
+ public int getItemCount() {
+ return data.size();
+ }
+
+ public Object getItem(int position) {
+ final int size = data.size();
+ if (size == 0) {
+ return null;
+ }
+ int index = (position + size) % size;
+ if (index >= 0 && index <= size - 1) {
+ return data.get(index);
+ }
+ return null;
+ }
+
+ public int getPosition(Object item) {
+ if (item == null) {
+ return 0;
+ }
+ return data.indexOf(item);
+ }
+
+ public int getCurrentPosition() {
+ return currentPosition;
+ }
+
+ public Object getCurrentItem() {
+ return getItem(currentPosition);
+ }
+
+ public int getVisibleItemCount() {
+ return visibleItemCount;
+ }
+
+ public void setVisibleItemCount(int count) {
+ visibleItemCount = count;
+ updateVisibleItemCount();
+ requestLayout();
+ }
+
+ public boolean isCyclicEnabled() {
+ return cyclicEnabled;
+ }
+
+ public void setCyclicEnabled(boolean isCyclic) {
+ this.cyclicEnabled = isCyclic;
+ computeFlingLimitYCoordinate();
+ invalidate();
+ }
+
+ public void setOnWheelChangedListener(OnWheelChangedListener listener) {
+ onWheelChangedListener = listener;
+ }
+
+ public void setFormatter(WheelFormatter formatter) {
+ this.formatter = formatter;
+ }
+
+ public List> getData() {
+ return data;
+ }
+
+ public void setData(List> newData) {
+ if (newData == null) {
+ newData = new ArrayList<>();
+ }
+ data = newData;
+ notifyDataSetChanged();
+ }
+
+ public void setDefaultValue(Object value) {
+ if (value == null) {
+ return;
+ }
+ int position = 0;
+ for (Object item : data) {
+ if (item.equals(value)) {
+ break;
+ }
+ if (item instanceof TextProvider) {
+ String text = ((TextProvider) item).provideText();
+ if (text.equals(value)) {
+ break;
+ }
+ }
+ position++;
+ }
+ setDefaultPosition(position);
+ }
+
+ public void setDefaultPosition(int position) {
+ position = Math.min(position, getItemCount() - 1);
+ position = Math.max(position, 0);
+ defaultItem = getItem(position);
+ defaultItemPosition = position;
+ currentPosition = position;
+ scrollOffsetYCoordinate = 0;
+ computeFlingLimitYCoordinate();
+ requestLayout();
+ invalidate();
+ }
+
+ public boolean isSameWidthEnabled() {
+ return sameWidthEnabled;
+ }
+
+ public void setSameWidthEnabled(boolean sameWidthEnabled) {
+ this.sameWidthEnabled = sameWidthEnabled;
+ computeTextWidthAndHeight();
+ requestLayout();
+ invalidate();
+ }
+
+ public String getMaxWidthText() {
+ return maxWidthText;
+ }
+
+ public void setMaxWidthText(String text) {
+ if (null == text) {
+ throw new NullPointerException("Maximum width text can not be null!");
+ }
+ maxWidthText = text;
+ computeTextWidthAndHeight();
+ requestLayout();
+ invalidate();
+ }
+
+ @ColorInt
+ public int getSelectedTextColor() {
+ return textColorSelected;
+ }
+
+ public void setSelectedTextColor(@ColorInt int color) {
+ textColorSelected = color;
+ computeCurrentItemRect();
+ invalidate();
+ }
+
+ @ColorInt
+ public int getTextColor() {
+ return textColor;
+ }
+
+ public void setTextColor(@ColorInt int color) {
+ textColor = color;
+ invalidate();
+ }
+
+ @Px
+ public int getTextSize() {
+ return textSize;
+ }
+
+ public void setTextSize(@Px int size) {
+ textSize = size;
+ paint.setTextSize(textSize);
+ computeTextWidthAndHeight();
+ requestLayout();
+ invalidate();
+ }
+
+ @Px
+ public int getItemSpace() {
+ return itemSpace;
+ }
+
+ public void setItemSpace(@Px int space) {
+ itemSpace = space;
+ requestLayout();
+ invalidate();
+ }
+
+ public boolean isIndicatorEnabled() {
+ return indicatorEnabled;
+ }
+
+ public void setIndicatorEnabled(boolean indicatorEnabled) {
+ this.indicatorEnabled = indicatorEnabled;
+ computeIndicatorRect();
+ invalidate();
+ }
+
+ @Px
+ public float getIndicatorSize() {
+ return indicatorSize;
+ }
+
+ public void setIndicatorSize(@Px float size) {
+ indicatorSize = size;
+ computeIndicatorRect();
+ invalidate();
+ }
+
+ @ColorInt
+ public int getIndicatorColor() {
+ return indicatorColor;
+ }
+
+ public void setIndicatorColor(@ColorInt int color) {
+ indicatorColor = color;
+ invalidate();
+ }
+
+ public boolean isCurtainEnabled() {
+ return curtainEnabled;
+ }
+
+ public void setCurtainEnabled(boolean curtainEnabled) {
+ this.curtainEnabled = curtainEnabled;
+ computeCurrentItemRect();
+ invalidate();
+ }
+
+ @ColorInt
+ public int getCurtainColor() {
+ return curtainColor;
+ }
+
+ public void setCurtainColor(@ColorInt int color) {
+ curtainColor = color;
+ invalidate();
+ }
+
+ public boolean isAtmosphericEnabled() {
+ return atmosphericEnabled;
+ }
+
+ public void setAtmosphericEnabled(boolean atmosphericEnabled) {
+ this.atmosphericEnabled = atmosphericEnabled;
+ invalidate();
+ }
+
+ public boolean isCurvedEnabled() {
+ return curvedEnabled;
+ }
+
+ public void setCurvedEnabled(boolean isCurved) {
+ this.curvedEnabled = isCurved;
+ requestLayout();
+ invalidate();
+ }
+
+ public int getCurvedMaxAngle() {
+ return curvedMaxAngle;
+ }
+
+ public void setCurvedMaxAngle(int curvedMaxAngle) {
+ this.curvedMaxAngle = curvedMaxAngle;
+ requestLayout();
+ invalidate();
+ }
+
+ @ItemTextAlign
+ public int getTextAlign() {
+ return textAlign;
+ }
+
+ public void setTextAlign(@ItemTextAlign int align) {
+ textAlign = align;
+ updatePaintTextAlign();
+ computeDrawnCenterCoordinate();
+ invalidate();
+ }
+
+ private void updatePaintTextAlign() {
+ switch (textAlign) {
+ case ItemTextAlign.LEFT:
+ paint.setTextAlign(Paint.Align.LEFT);
+ break;
+ case ItemTextAlign.RIGHT:
+ paint.setTextAlign(Paint.Align.RIGHT);
+ break;
+ case ItemTextAlign.CENTER:
+ default:
+ paint.setTextAlign(Paint.Align.CENTER);
+ break;
+ }
+ }
+
+ public Typeface getTypeface() {
+ if (null != paint) {
+ return paint.getTypeface();
+ }
+ return null;
+ }
+
+ public void setTypeface(Typeface typeface) {
+ if (paint == null || typeface == null) {
+ return;
+ }
+ paint.setTypeface(typeface);
+ computeTextWidthAndHeight();
+ requestLayout();
+ invalidate();
+ }
+
+ public void notifyDataSetChanged() {
+ defaultItem = getItem(0);
+ defaultItemPosition = 0;
+ currentPosition = 0;
+ scrollOffsetYCoordinate = 0;
+ updatePaintTextAlign();
+ computeTextWidthAndHeight();
+ computeFlingLimitYCoordinate();
+ requestLayout();
+ invalidate();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
+ final int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
+ final int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
+ // Correct sizes of original content
+ int resultWidth = textMaxWidth;
+ int resultHeight = textMaxHeight * visibleItemCount + itemSpace * (visibleItemCount - 1);
+ // Correct view sizes again if curved is enable
+ if (curvedEnabled) {
+ resultHeight = (int) (2 * resultHeight / Math.PI);
+ }
+ // Consideration padding influence the view sizes
+ resultWidth += getPaddingLeft() + getPaddingRight();
+ resultHeight += getPaddingTop() + getPaddingBottom();
+ // Consideration sizes of parent can influence the view sizes
+ resultWidth = measureSize(modeWidth, sizeWidth, resultWidth);
+ resultHeight = measureSize(modeHeight, sizeHeight, resultHeight);
+ setMeasuredDimension(resultWidth, resultHeight);
+ }
+
+ private int measureSize(int mode, int sizeExpect, int sizeActual) {
+ int realSize;
+ if (mode == MeasureSpec.EXACTLY) {
+ realSize = sizeExpect;
+ } else {
+ realSize = sizeActual;
+ if (mode == MeasureSpec.AT_MOST) {
+ realSize = Math.min(realSize, sizeExpect);
+ }
+ }
+ return realSize;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int ow, int oh) {
+ // Set content region
+ rectDrawn.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
+ getHeight() - getPaddingBottom());
+ // Get the center coordinates of content region
+ wheelCenterXCoordinate = rectDrawn.centerX();
+ wheelCenterYCoordinate = rectDrawn.centerY();
+ // Correct item drawn center
+ computeDrawnCenterCoordinate();
+ halfWheelHeight = rectDrawn.height() / 2;
+ itemHeight = rectDrawn.height() / visibleItemCount;
+ halfItemHeight = itemHeight / 2;
+ // Initialize fling max Y-coordinates
+ computeFlingLimitYCoordinate();
+ // Correct region of indicator
+ computeIndicatorRect();
+ // Correct region of current select item
+ computeCurrentItemRect();
+ }
+
+ private void computeDrawnCenterCoordinate() {
+ switch (textAlign) {
+ case ItemTextAlign.LEFT:
+ drawnCenterXCoordinate = rectDrawn.left;
+ break;
+ case ItemTextAlign.RIGHT:
+ drawnCenterXCoordinate = rectDrawn.right;
+ break;
+ case ItemTextAlign.CENTER:
+ default:
+ drawnCenterXCoordinate = wheelCenterXCoordinate;
+ break;
+ }
+ drawnCenterYCoordinate = (int) (wheelCenterYCoordinate -
+ ((paint.ascent() + paint.descent()) / 2));
+ }
+
+ private void computeFlingLimitYCoordinate() {
+ int currentItemOffset = defaultItemPosition * itemHeight;
+ minFlingYCoordinate = cyclicEnabled ? Integer.MIN_VALUE
+ : -itemHeight * (getItemCount() - 1) + currentItemOffset;
+ maxFlingYCoordinate = cyclicEnabled ? Integer.MAX_VALUE : currentItemOffset;
+ }
+
+ private void computeIndicatorRect() {
+ if (!indicatorEnabled) {
+ return;
+ }
+ int halfIndicatorSize = (int) (indicatorSize / 2f);
+ int indicatorHeadCenterYCoordinate = wheelCenterYCoordinate + halfItemHeight;
+ int indicatorFootCenterYCoordinate = wheelCenterYCoordinate - halfItemHeight;
+ rectIndicatorHead.set(rectDrawn.left, indicatorHeadCenterYCoordinate - halfIndicatorSize,
+ rectDrawn.right, indicatorHeadCenterYCoordinate + halfIndicatorSize);
+ rectIndicatorFoot.set(rectDrawn.left, indicatorFootCenterYCoordinate - halfIndicatorSize,
+ rectDrawn.right, indicatorFootCenterYCoordinate + halfIndicatorSize);
+ }
+
+ private void computeCurrentItemRect() {
+ if (!curtainEnabled && textColorSelected == -1) {
+ return;
+ }
+ rectCurrentItem.set(rectDrawn.left, wheelCenterYCoordinate - halfItemHeight,
+ rectDrawn.right, wheelCenterYCoordinate + halfItemHeight);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (null != onWheelChangedListener) {
+ onWheelChangedListener.onWheelScrolled(this, scrollOffsetYCoordinate);
+ }
+ if (itemHeight - halfDrawnItemCount <= 0) {
+ return;
+ }
+ drawAllItemText(canvas);
+ drawCurtain(canvas);
+ drawIndicator(canvas);
+ }
+
+ private void drawAllItemText(Canvas canvas) {
+ int drawnDataStartPos = -1 * scrollOffsetYCoordinate / itemHeight - halfDrawnItemCount;
+ for (int drawnDataPosition = drawnDataStartPos + defaultItemPosition,
+ drawnOffsetPos = -1 * halfDrawnItemCount;
+ drawnDataPosition < drawnDataStartPos + defaultItemPosition + drawnItemCount;
+ drawnDataPosition++, drawnOffsetPos++) {
+
+ paint.setColor(textColor);
+ paint.setStyle(Paint.Style.FILL);
+
+ int drawnItemCenterYCoordinate = drawnCenterYCoordinate + (drawnOffsetPos * itemHeight)
+ + scrollOffsetYCoordinate % itemHeight;
+
+ int centerYCoordinateAbs = Math.abs(drawnCenterYCoordinate - drawnItemCenterYCoordinate);
+ // Correct ratio of item's drawn center to wheel center
+ float ratio = (drawnCenterYCoordinate - centerYCoordinateAbs - rectDrawn.top) * 1f /
+ (drawnCenterYCoordinate - rectDrawn.top);
+ float degree = computeDegree(drawnItemCenterYCoordinate, ratio);
+ float distanceToCenter = computeYCoordinateAtAngle(degree);
+
+ if (curvedEnabled) {
+ int transXCoordinate = wheelCenterXCoordinate;
+ switch (textAlign) {
+ case ItemTextAlign.LEFT:
+ transXCoordinate = rectDrawn.left;
+ break;
+ case ItemTextAlign.RIGHT:
+ transXCoordinate = rectDrawn.right;
+ break;
+ case ItemTextAlign.CENTER:
+ default:
+ break;
+ }
+ float transYCoordinate = wheelCenterYCoordinate - distanceToCenter;
+
+ camera.save();
+ camera.rotateX(degree);
+ camera.getMatrix(matrixRotate);
+ camera.restore();
+ matrixRotate.preTranslate(-transXCoordinate, -transYCoordinate);
+ matrixRotate.postTranslate(transXCoordinate, transYCoordinate);
+
+ camera.save();
+ camera.translate(0, 0, computeDepth(degree));
+ camera.getMatrix(matrixDepth);
+ camera.restore();
+ matrixDepth.preTranslate(-transXCoordinate, -transYCoordinate);
+ matrixDepth.postTranslate(transXCoordinate, transYCoordinate);
+ matrixRotate.postConcat(matrixDepth);
+ }
+
+ computeAndSetAtmospheric(centerYCoordinateAbs);
+ // Correct item's drawn center Y coordinate base on curved state
+ float drawCenterYCoordinate = curvedEnabled ? drawnCenterYCoordinate - distanceToCenter
+ : drawnItemCenterYCoordinate;
+ String data = obtainItemText(drawnDataPosition);
+ if (paint.measureText(data) - getMeasuredWidth() > 0) {
+ // 超出控件宽度则省略部分文字
+ data = data.substring(0, data.length() - 3) + "...";
+ }
+ drawItemText(canvas, data, drawCenterYCoordinate);
+ }
+ }
+
+ private void drawItemText(Canvas canvas, String data, float drawCenterYCoordinate) {
+ // Judges need to draw different color for current item or not
+ if (textColorSelected == -1) {
+ canvas.save();
+ canvas.clipRect(rectDrawn);
+ if (curvedEnabled) {
+ canvas.concat(matrixRotate);
+ }
+ canvas.drawText(data, drawnCenterXCoordinate, drawCenterYCoordinate, paint);
+ canvas.restore();
+ return;
+ }
+
+ canvas.save();
+ if (curvedEnabled) {
+ canvas.concat(matrixRotate);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ canvas.clipOutRect(rectCurrentItem);
+ } else {
+ canvas.clipRect(rectCurrentItem, Region.Op.DIFFERENCE);
+ }
+ canvas.drawText(data, drawnCenterXCoordinate, drawCenterYCoordinate, paint);
+ canvas.restore();
+
+ paint.setColor(textColorSelected);
+ canvas.save();
+ if (curvedEnabled) {
+ canvas.concat(matrixRotate);
+ }
+ canvas.clipRect(rectCurrentItem);
+ canvas.drawText(data, drawnCenterXCoordinate, drawCenterYCoordinate, paint);
+ canvas.restore();
+ }
+
+ private float computeDegree(int drawnItemCenterYCoordinate, float ratio) {
+ // Correct unit
+ int unit = 0;
+ if (drawnItemCenterYCoordinate > drawnCenterYCoordinate) {
+ unit = 1;
+ } else if (drawnItemCenterYCoordinate < drawnCenterYCoordinate) {
+ unit = -1;
+ }
+ return clamp((-(1 - ratio) * curvedMaxAngle * unit), -curvedMaxAngle, curvedMaxAngle);
+ }
+
+ private float clamp(float value, float min, float max) {
+ if (value < min) {
+ return min;
+ }
+ return Math.min(value, max);
+ }
+
+ private String obtainItemText(int drawnDataPosition) {
+ String data = "";
+ final int itemCount = getItemCount();
+ if (cyclicEnabled) {
+ if (itemCount != 0) {
+ int actualPosition = drawnDataPosition % itemCount;
+ actualPosition = actualPosition < 0 ? (actualPosition + itemCount) : actualPosition;
+ data = formatItem(actualPosition);
+ }
+ } else {
+ if (isPositionInRange(drawnDataPosition, itemCount)) {
+ data = formatItem(drawnDataPosition);
+ }
+ }
+ return data;
+ }
+
+ public String formatItem(int position) {
+ Object item = getItem(position);
+ if (item instanceof TextProvider) {
+ return ((TextProvider) item).provideText();
+ }
+ if (formatter != null) {
+ return formatter.formatItem(item);
+ }
+ return item.toString();
+ }
+
+ private void computeAndSetAtmospheric(int abs) {
+ if (atmosphericEnabled) {
+ int alpha = (int) ((drawnCenterYCoordinate - abs) * 1.0F / drawnCenterYCoordinate * 255);
+ alpha = Math.max(alpha, 0);
+ paint.setAlpha(alpha);
+ }
+ }
+
+ private void drawCurtain(Canvas canvas) {
+ // Need to draw curtain or not
+ if (curtainEnabled) {
+ int red = Color.red(curtainColor);
+ int green = Color.green(curtainColor);
+ int blue = Color.blue(curtainColor);
+ paint.setColor(Color.argb(128, red, green, blue));
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawRect(rectCurrentItem, paint);
+ }
+ }
+
+ private void drawIndicator(Canvas canvas) {
+ // Need to draw indicator or not
+ if (indicatorEnabled) {
+ paint.setColor(indicatorColor);
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawRect(rectIndicatorHead, paint);
+ canvas.drawRect(rectIndicatorFoot, paint);
+ }
+ }
+
+ private boolean isPositionInRange(int position, int itemCount) {
+ return position >= 0 && position < itemCount;
+ }
+
+ private float computeYCoordinateAtAngle(float degree) {
+ // Compute y-coordinate for item at degree.
+ return sinDegree(degree) / sinDegree(curvedMaxAngle) * halfWheelHeight;
+ }
+
+ private float sinDegree(float degree) {
+ return (float) Math.sin(Math.toRadians(degree));
+ }
+
+ private int computeDepth(float degree) {
+ return (int) (halfWheelHeight - Math.cos(Math.toRadians(degree)) * halfWheelHeight);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (isEnabled()) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ handleActionDown(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ handleActionMove(event);
+ break;
+ case MotionEvent.ACTION_UP:
+ handleActionUp(event);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ handleActionCancel(event);
+ break;
+ default:
+ break;
+ }
+ }
+ if (isClick) {
+ //onTouchEvent should call performClick when a click is detected
+ performClick();
+ }
+ return true;
+ }
+
+ private void handleActionDown(MotionEvent event) {
+ if (null != getParent()) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ obtainOrClearTracker();
+ tracker.addMovement(event);
+ if (!scroller.isFinished()) {
+ scroller.abortAnimation();
+ isForceFinishScroll = true;
+ }
+ downPointYCoordinate = lastPointYCoordinate = (int) event.getY();
+ }
+
+ private void handleActionMove(MotionEvent event) {
+ int endPoint = computeDistanceToEndPoint(scroller.getFinalY() % itemHeight);
+ if (Math.abs(downPointYCoordinate - event.getY()) < touchSlop && endPoint > 0) {
+ isClick = true;
+ return;
+ }
+ isClick = false;
+ if (null != tracker) {
+ tracker.addMovement(event);
+ }
+ if (null != onWheelChangedListener) {
+ onWheelChangedListener.onWheelScrollStateChanged(this, SCROLL_STATE_DRAGGING);
+ }
+ // Scroll WheelPicker's content
+ float move = event.getY() - lastPointYCoordinate;
+ if (Math.abs(move) < 1) {
+ return;
+ }
+ scrollOffsetYCoordinate += move;
+ lastPointYCoordinate = (int) event.getY();
+ invalidate();
+ }
+
+ private void handleActionUp(MotionEvent event) {
+ if (null != getParent()) {
+ getParent().requestDisallowInterceptTouchEvent(false);
+ }
+ if (isClick) {
+ return;
+ }
+ int yVelocity = 0;
+ if (null != tracker) {
+ tracker.addMovement(event);
+ tracker.computeCurrentVelocity(1000, maximumVelocity);
+ yVelocity = (int) tracker.getYVelocity();
+ }
+
+ // Judge scroll or fling base on current velocity
+ isForceFinishScroll = false;
+ if (Math.abs(yVelocity) > minimumVelocity) {
+ scroller.fling(0, scrollOffsetYCoordinate, 0, yVelocity, 0,
+ 0, minFlingYCoordinate, maxFlingYCoordinate);
+ int endPoint = computeDistanceToEndPoint(scroller.getFinalY() % itemHeight);
+ scroller.setFinalY(scroller.getFinalY() + endPoint);
+ } else {
+ int endPoint = computeDistanceToEndPoint(scrollOffsetYCoordinate % itemHeight);
+ scroller.startScroll(0, scrollOffsetYCoordinate, 0, endPoint);
+ }
+ // Correct coordinates
+ if (!cyclicEnabled) {
+ if (scroller.getFinalY() > maxFlingYCoordinate) {
+ scroller.setFinalY(maxFlingYCoordinate);
+ } else if (scroller.getFinalY() < minFlingYCoordinate) {
+ scroller.setFinalY(minFlingYCoordinate);
+ }
+ }
+ handler.post(this);
+ cancelTracker();
+ }
+
+ private void handleActionCancel(MotionEvent event) {
+ if (null != getParent()) {
+ getParent().requestDisallowInterceptTouchEvent(false);
+ }
+ cancelTracker();
+ }
+
+ private void obtainOrClearTracker() {
+ if (null == tracker) {
+ tracker = VelocityTracker.obtain();
+ } else {
+ tracker.clear();
+ }
+ }
+
+ private void cancelTracker() {
+ if (null != tracker) {
+ tracker.recycle();
+ tracker = null;
+ }
+ }
+
+ @Override
+ public boolean performClick() {
+ return super.performClick();
+ }
+
+ private int computeDistanceToEndPoint(int remainder) {
+ if (Math.abs(remainder) > halfItemHeight) {
+ if (scrollOffsetYCoordinate < 0) {
+ return -itemHeight - remainder;
+ } else {
+ return itemHeight - remainder;
+ }
+ } else {
+ return -1 * remainder;
+ }
+ }
+
+ @Override
+ public void run() {
+ if (itemHeight == 0) {
+ return;
+ }
+ int itemCount = getItemCount();
+ if (itemCount == 0) {
+ return;
+ }
+ if (scroller.isFinished() && !isForceFinishScroll) {
+ int position = computePosition(itemCount);
+ position = position < 0 ? position + itemCount : position;
+ currentPosition = position;
+ if (null != onWheelChangedListener) {
+ onWheelChangedListener.onWheelSelected(this, position);
+ onWheelChangedListener.onWheelScrollStateChanged(this, SCROLL_STATE_IDLE);
+ }
+ postInvalidate();
+ return;
+ }
+ // Scroll not finished
+ if (scroller.computeScrollOffset()) {
+ if (null != onWheelChangedListener) {
+ onWheelChangedListener.onWheelScrollStateChanged(this, SCROLL_STATE_SCROLLING);
+ }
+ scrollOffsetYCoordinate = scroller.getCurrY();
+ int position = computePosition(itemCount);
+ if (lastScrollPosition != position) {
+ if (position == 0 && lastScrollPosition == itemCount - 1) {
+ if (null != onWheelChangedListener) {
+ onWheelChangedListener.onWheelLoopFinished(this);
+ }
+ }
+ lastScrollPosition = position;
+ }
+ postInvalidate();
+ handler.postDelayed(this, 16);
+ }
+ }
+
+ private int computePosition(int itemCount) {
+ return (-1 * scrollOffsetYCoordinate / itemHeight + defaultItemPosition) % itemCount;
+ }
+
+ public final void scrollTo(final int position) {
+ if (position == currentPosition) {
+ return;
+ }
+ final int differencesLines = currentPosition - position;
+ final int newScrollOffsetYCoordinate = scrollOffsetYCoordinate + (differencesLines * itemHeight);
+ ValueAnimator animator = ValueAnimator.ofInt(scrollOffsetYCoordinate, newScrollOffsetYCoordinate);
+ animator.setDuration(300);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ scrollOffsetYCoordinate = (int) animation.getAnimatedValue();
+ invalidate();
+ }
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ currentPosition = position;
+ }
+ });
+ animator.start();
+ }
+
+}
diff --git a/app/src/main/java/cn/qqtheme/androidpicker/AddressInitTask.java b/app/src/main/java/cn/qqtheme/androidpicker/AddressInitTask.java
deleted file mode 100644
index aeb6d54c..00000000
--- a/app/src/main/java/cn/qqtheme/androidpicker/AddressInitTask.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package cn.qqtheme.androidpicker;
-
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.os.AsyncTask;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-import androidx.annotation.WorkerThread;
-import cn.qqtheme.framework.entity.City;
-import cn.qqtheme.framework.entity.County;
-import cn.qqtheme.framework.entity.Province;
-import cn.qqtheme.framework.util.ConvertUtils;
-
-/**
- * 获取地址数据
- *
- * @author 李玉江[QQ:1032694760]
- * @since 2017/10/13
- */
-public class AddressInitTask extends AsyncTask> {
- private WeakReference activityReference;// 2018/2/1 StaticFieldLeak
- private ProgressDialog dialog;
- private InitCallback callback;
- private ArrayList provinces;
-
- public AddressInitTask(Activity activity, InitCallback callback) {
- this.activityReference = new WeakReference<>(activity);
- this.callback = callback;
- }
-
- @Override
- protected void onPreExecute() {
- Activity activity = activityReference.get();
- if (activity != null) {
- dialog = ProgressDialog.show(activity, null, "正在初始化数据...", true, true);
- }
- }
-
- @Override
- protected ArrayList doInBackground(Void... params) {
- Activity activity = activityReference.get();
- if (activity == null) {
- return null;
- }
- try {
- String data = ConvertUtils.toString(activity.getAssets().open("city.txt"));
- return parseData(data);
- } catch (java.io.IOException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(ArrayList result) {
- if (dialog != null) {
- dialog.dismiss();
- }
- if (result == null || result.size() == 0) {
- callback.onDataInitFailure();
- } else {
- callback.onDataInitSuccess(result);
- }
- }
-
- @WorkerThread
- private ArrayList parseData(String data) {
- if (provinces != null && provinces.size() > 0) {
- return provinces;
- }
- provinces = new ArrayList<>();
- String[] fullCodeAndNames = data.split(";");
- for (String fullCodeAndName : fullCodeAndNames) {
- String[] codeAndName = fullCodeAndName.split(",");
- if (codeAndName.length != 2) {
- continue;
- }
- String code = codeAndName[0];
- String name = codeAndName[1];
- if (code.substring(2, 6).equals("0000")) {
- //省份
- Province province = new Province();
- province.setAreaId(code);
- province.setAreaName(name);
- province.setCities(new ArrayList());
- provinces.add(province);
- } else if (code.substring(4, 6).equals("00")) {
- //地市
- Province province = findProvinceByCode(code.substring(0, 2));
- if (province != null) {
- City city = new City();
- city.setAreaId(code);
- city.setAreaName(name);
- city.setCounties(new ArrayList());
- province.getCities().add(city);
- }
- } else {
- //区县
- City city = findCityByCode(code.substring(0, 2), code.substring(2, 4));
- if (city != null) {
- County county = new County();
- county.setAreaId(code);
- county.setAreaName(name);
- city.getCounties().add(county);
- }
- }
- }
- return provinces;
- }
-
- private Province findProvinceByCode(String provinceCode) {
- for (Province province : provinces) {
- if (province.getAreaId().substring(0, 2).equals(provinceCode)) {
- return province;
- }
- }
- return null;
- }
-
- private City findCityByCode(String provinceCode, String cityCode) {
- for (Province province : provinces) {
- List cities = province.getCities();
- for (City city : cities) {
- if (province.getAreaId().substring(0, 2).equals(provinceCode) &&
- city.getAreaId().substring(2, 4).equals(cityCode)) {
- return city;
- }
- }
- }
- return null;
- }
-
- public interface InitCallback {
-
- void onDataInitFailure();
-
- void onDataInitSuccess(ArrayList provinces);
-
- }
-
-}
diff --git a/app/src/main/java/cn/qqtheme/androidpicker/AddressPickTask.java b/app/src/main/java/cn/qqtheme/androidpicker/AddressPickTask.java
deleted file mode 100644
index c542ef01..00000000
--- a/app/src/main/java/cn/qqtheme/androidpicker/AddressPickTask.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package cn.qqtheme.androidpicker;
-
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.os.AsyncTask;
-
-import com.alibaba.fastjson.JSON;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-import cn.qqtheme.framework.entity.Province;
-import cn.qqtheme.framework.picker.AddressPicker;
-import cn.qqtheme.framework.util.ConvertUtils;
-
-/**
- * 获取地址数据并显示地址选择器
- *
- * @author 李玉江[QQ:1032694760]
- * @since 2015/12/15
- */
-public class AddressPickTask extends AsyncTask> {
- private WeakReference activityReference;// 2018/6/1 StaticFieldLeak
- private ProgressDialog dialog;
- private Callback callback;
- private String selectedProvince = "", selectedCity = "", selectedCounty = "";
- private boolean hideProvince = false;
- private boolean hideCounty = false;
-
- public AddressPickTask(Activity activity) {
- this.activityReference = new WeakReference<>(activity);
- }
-
- public void setHideProvince(boolean hideProvince) {
- this.hideProvince = hideProvince;
- }
-
- public void setHideCounty(boolean hideCounty) {
- this.hideCounty = hideCounty;
- }
-
- public void setCallback(Callback callback) {
- this.callback = callback;
- }
-
- @Override
- protected void onPreExecute() {
- Activity activity = activityReference.get();
- if (activity == null) {
- return;
- }
- dialog = ProgressDialog.show(activity, null, "正在初始化数据...", true, true);
- }
-
- @Override
- protected ArrayList doInBackground(String... params) {
- if (params != null) {
- switch (params.length) {
- case 1:
- selectedProvince = params[0];
- break;
- case 2:
- selectedProvince = params[0];
- selectedCity = params[1];
- break;
- case 3:
- selectedProvince = params[0];
- selectedCity = params[1];
- selectedCounty = params[2];
- break;
- default:
- break;
- }
- }
- ArrayList data = new ArrayList<>();
- try {
- Activity activity = activityReference.get();
- if (activity != null) {
- String json = ConvertUtils.toString(activity.getAssets().open("city.json"));
- data.addAll(JSON.parseArray(json, Province.class));
- }
- } catch (java.io.IOException e) {
- e.printStackTrace();
- }
- return data;
- }
-
- @Override
- protected void onPostExecute(ArrayList result) {
- if (dialog != null) {
- dialog.dismiss();
- }
- if (result.size() > 0) {
- Activity activity = activityReference.get();
- if (activity == null) {
- return;
- }
- AddressPicker picker = new AddressPicker(activity, result);
- picker.setHideProvince(hideProvince);
- picker.setHideCounty(hideCounty);
- if (hideCounty) {
- picker.setColumnWeight(1 / 3.0f, 2 / 3.0f);//将屏幕分为3份,省级和地级的比例为1:2
- } else {
- picker.setColumnWeight(2 / 8.0f, 3 / 8.0f, 3 / 8.0f);//省级、地级和县级的比例为2:3:3
- }
- picker.setSelectedItem(selectedProvince, selectedCity, selectedCounty);
- picker.setOnAddressPickListener(callback);
- picker.show();
- } else {
- callback.onAddressInitFailed();
- }
- }
-
- public interface Callback extends AddressPicker.OnAddressPickListener {
-
- void onAddressInitFailed();
-
- }
-
-}
diff --git a/app/src/main/java/cn/qqtheme/androidpicker/AppManager.java b/app/src/main/java/cn/qqtheme/androidpicker/AppManager.java
deleted file mode 100644
index 5ee0bef7..00000000
--- a/app/src/main/java/cn/qqtheme/androidpicker/AppManager.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package cn.qqtheme.androidpicker;
-
-import android.app.Activity;
-import android.app.Service;
-import android.os.Bundle;
-
-import java.util.LinkedList;
-
-import cn.qqtheme.framework.util.LogUtils;
-
-/**
- * Activity及Service管理,以便实现退出功能
- *
- * @author 李玉江[QQ:1032694760]
- * @since 2015/12/17
- */
-public class AppManager {
- //本类的实例
- private static AppManager instance;
- //保存所有Activity
- private LinkedList activities = new LinkedList();
- //保存所有Service
- private LinkedList services = new LinkedList();
-
- public static AppManager getInstance() {
- if (instance == null) {
- instance = new AppManager();
- }
- return instance;
- }
-
- /**
- * 注册Activity以便集中“finish()”
- *
- * @param activity the activity
- * @see Activity#onCreate(Bundle)
- * @see Activity#onStart()
- */
- public void addActivity(Activity activity) {
- activities.add(activity);
- }
-
- /**
- * 移除Activity.
- *
- * @param activity the activity
- * @see Activity#onDestroy()
- * @see Activity#onStop()
- */
- public void removeActivity(Activity activity) {
- activities.remove(activity);
- }
-
- /**
- * 所有的Activity
- *
- * @return the activities
- */
- public LinkedList getActivities() {
- return activities;
- }
-
- /**
- * 最后加入的Activity
- *
- * @return the activity
- */
- public Activity getLastActivity() {
- Activity activity = activities.getLast();
- LogUtils.debug(this, "last activity is " + activity.getClass().getName());
- return activity;
- }
-
- /**
- * 注册Service以便集中“stopSelf()”
- *
- * @param service the service
- */
- public void addService(Service service) {
- services.add(service);
- }
-
- /**
- * Remove service.
- *
- * @param service the service
- */
- public void removeService(Service service) {
- services.remove(service);
- }
-
- /**
- * 所有的Service
- *
- * @return the services
- */
- public LinkedList getServices() {
- return services;
- }
-
- /**
- * 退出软件
- */
- public void exitApp() {
- clearActivitiesAndServices();
- android.os.Process.killProcess(android.os.Process.myPid());
- System.exit(0);//normal exit application
- }
-
- /**
- * 当内存不足时,需要清除已打开的Activity及Service
- *
- * @see android.app.Application#onLowMemory()
- */
- public void clearActivitiesAndServices() {
- for (Activity activity : activities) {
- if (!activity.isFinishing()) {
- activity.finish();
- }
- }
- for (Service service : services) {
- service.stopSelf();
- }
- }
-
-}
diff --git a/app/src/main/java/cn/qqtheme/androidpicker/CustomHeaderAndFooterPicker.java b/app/src/main/java/cn/qqtheme/androidpicker/CustomHeaderAndFooterPicker.java
deleted file mode 100644
index 10513cf7..00000000
--- a/app/src/main/java/cn/qqtheme/androidpicker/CustomHeaderAndFooterPicker.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package cn.qqtheme.androidpicker;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.app.Activity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.animation.AccelerateInterpolator;
-import android.widget.Button;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-import cn.qqtheme.framework.picker.OptionPicker;
-import cn.qqtheme.framework.widget.WheelView;
-
-/**
- * 自定义顶部及底部
- *
- * Author:李玉江[QQ:1032694760]
- * Email:liyujiang_tk@yeah.net
- * DateTime:2016/1/29 14:47
- * Builder:Android Studio
- */
-public class CustomHeaderAndFooterPicker extends OptionPicker implements OptionPicker.OnWheelListener {
- private TextView titleView;
-
- public CustomHeaderAndFooterPicker(Activity activity) {
- super(activity, new String[]{
- "Java/Android", "PHP/MySQL", "HTML/CSS/JS", "C/C++"
- });
- setSelectedIndex(1);
- setDividerRatio(WheelView.DividerConfig.FILL);
- setOnWheelListener(this);
- }
-
- @Override
- protected void showAfter() {
- View rootView = getRootView();
- AnimatorSet animatorSet = new AnimatorSet();
- ObjectAnimator alpha = ObjectAnimator.ofFloat(rootView, "alpha", 0, 1);
- ObjectAnimator translation = ObjectAnimator.ofFloat(rootView, "translationY", 300, 0);
- animatorSet.playTogether(alpha, translation);
- animatorSet.setDuration(1000);
- animatorSet.setInterpolator(new AccelerateInterpolator());
- animatorSet.start();
- }
-
- @Override
- public void dismiss() {
- View rootView = getRootView();
- AnimatorSet animatorSet = new AnimatorSet();
- ObjectAnimator alpha = ObjectAnimator.ofFloat(rootView, "alpha", 1, 0);
- ObjectAnimator translation = ObjectAnimator.ofFloat(rootView, "translationX", 0, rootView.getWidth());
- ObjectAnimator rotation = ObjectAnimator.ofFloat(rootView, "rotation", 0, 120);
- animatorSet.playTogether(alpha, translation, rotation);
- animatorSet.setDuration(1000);
- animatorSet.setInterpolator(new AccelerateInterpolator());
- animatorSet.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
-
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- dismissImmediately();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
-
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
-
- }
- });
- animatorSet.start();
- }
-
- @Nullable
- @Override
- protected View makeHeaderView() {
- View view = LayoutInflater.from(activity).inflate(R.layout.picker_header, null);
- titleView = (TextView) view.findViewById(R.id.picker_title);
- titleView.setText(titleText);
- view.findViewById(R.id.picker_close).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- dismiss();
- }
- });
- return view;
- }
-
- @Nullable
- @Override
- protected View makeFooterView() {
- View view = LayoutInflater.from(activity).inflate(R.layout.picker_footer, null);
- Button submitView = (Button) view.findViewById(R.id.picker_submit);
- submitView.setText(submitText);
- submitView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- dismiss();
- onSubmit();
- }
- });
- Button cancelView = (Button) view.findViewById(R.id.picker_cancel);
- cancelView.setText(cancelText);
- cancelView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- dismiss();
- onCancel();
- }
- });
- return view;
- }
-
- @Override
- public void onWheeled(int index, String item) {
- if (titleView != null) {
- titleView.setText(item);
- }
- }
-
- @Override
- public void onSubmit() {
- super.onSubmit();
- }
-
- @Override
- protected void onCancel() {
- super.onCancel();
- }
-
-}
diff --git a/app/src/main/java/cn/qqtheme/androidpicker/MainActivity.java b/app/src/main/java/cn/qqtheme/androidpicker/MainActivity.java
deleted file mode 100644
index d5d89328..00000000
--- a/app/src/main/java/cn/qqtheme/androidpicker/MainActivity.java
+++ /dev/null
@@ -1,587 +0,0 @@
-package cn.qqtheme.androidpicker;
-
-import android.content.Intent;
-import android.graphics.Color;
-import android.net.Uri;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import com.alibaba.fastjson.JSON;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.List;
-import java.util.Locale;
-
-import cn.qqtheme.framework.entity.City;
-import cn.qqtheme.framework.entity.County;
-import cn.qqtheme.framework.entity.Province;
-import cn.qqtheme.framework.picker.AddressPicker;
-import cn.qqtheme.framework.picker.ColorPicker;
-import cn.qqtheme.framework.picker.DatePicker;
-import cn.qqtheme.framework.picker.DateTimePicker;
-import cn.qqtheme.framework.picker.DoublePicker;
-import cn.qqtheme.framework.picker.FilePicker;
-import cn.qqtheme.framework.picker.LinkagePicker;
-import cn.qqtheme.framework.picker.MultiplePicker;
-import cn.qqtheme.framework.picker.NumberPicker;
-import cn.qqtheme.framework.picker.OptionPicker;
-import cn.qqtheme.framework.picker.SinglePicker;
-import cn.qqtheme.framework.picker.TimePicker;
-import cn.qqtheme.framework.util.ConvertUtils;
-import cn.qqtheme.framework.util.DateUtils;
-import cn.qqtheme.framework.util.LogUtils;
-import cn.qqtheme.framework.util.StorageUtils;
-import cn.qqtheme.framework.widget.WheelView;
-
-public class MainActivity extends BaseActivity {
-
- @Override
- protected View getContentView() {
- return inflateView(R.layout.activity_main);
- }
-
- @Override
- protected void setContentViewAfter(View contentView) {
-
- }
-
- @Override
- public void onBackPressed() {
- AppManager.getInstance().exitApp();
- }
-
- private void showToast(String msg) {
- Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
- }
-
- public void onNestView(View view) {
- startActivity(new Intent(this, NestActivity.class));
- }
-
- public void onAnimationStyle(View view) {
- final NumberPicker picker = new NumberPicker(this);
- picker.setItemWidth(200);
- View headerView = View.inflate(activity, R.layout.picker_header, null);
- final TextView titleView = (TextView) headerView.findViewById(R.id.picker_title);
- titleView.setText("自定义顶部视图");
- headerView.findViewById(R.id.picker_close).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- picker.dismiss();
- }
- });
- picker.setHeaderView(headerView);
- picker.setAnimationStyle(R.style.Animation_CustomPopup);
- picker.setCycleDisable(false);
- picker.setOffset(5);//偏移量
- picker.setRange(10.5, 20, 1.5);//数字范围
- picker.setSelectedItem(18.0);
- picker.setLabel("℃");
- picker.setOnWheelListener(new NumberPicker.OnWheelListener() {
- @Override
- public void onWheeled(int index, Number item) {
- titleView.setText(String.valueOf(item.floatValue()));
- }
- });
- picker.show();
- }
-
- public void onAnimator(View view) {
- CustomHeaderAndFooterPicker picker = new CustomHeaderAndFooterPicker(this);
- picker.setOffset(3);//显示的条目的偏移量,条数为(offset*2+1)
- picker.setGravity(Gravity.CENTER);//居中
- picker.setOnOptionPickListener(new OptionPicker.OnOptionPickListener() {
- @Override
- public void onOptionPicked(int position, String option) {
- showToast("index=" + position + ", item=" + option);
- }
- });
- picker.show();
- }
-
- public void onYearMonthDayPicker(View view) {
- final DatePicker picker = new DatePicker(this);
- picker.setCanceledOnTouchOutside(true);
- picker.setUseWeight(true);
- picker.setTopPadding(ConvertUtils.toPx(this, 10));
- picker.setRangeEnd(2111, 1, 11);
- picker.setRangeStart(2016, 8, 29);
- picker.setSelectedItem(2050, 10, 14);
- picker.setResetWhileWheel(false);
- picker.setOnDatePickListener(new DatePicker.OnYearMonthDayPickListener() {
- @Override
- public void onDatePicked(String year, String month, String day) {
- showToast(year + "-" + month + "-" + day);
- }
- });
- picker.setOnWheelListener(new DatePicker.OnWheelListener() {
- @Override
- public void onYearWheeled(int index, String year) {
- picker.setTitleText(year + "-" + picker.getSelectedMonth() + "-" + picker.getSelectedDay());
- }
-
- @Override
- public void onMonthWheeled(int index, String month) {
- picker.setTitleText(picker.getSelectedYear() + "-" + month + "-" + picker.getSelectedDay());
- }
-
- @Override
- public void onDayWheeled(int index, String day) {
- picker.setTitleText(picker.getSelectedYear() + "-" + picker.getSelectedMonth() + "-" + day);
- }
- });
- picker.show();
- }
-
-
- public void onYearMonthDayTimePicker(View view) {
- DateTimePicker picker = new DateTimePicker(this, DateTimePicker.HOUR_24);
- picker.setDateRangeStart(2017, 1, 1);
- picker.setDateRangeEnd(2025, 11, 11);
- picker.setTimeRangeStart(9, 0);
- picker.setTimeRangeEnd(20, 30);
- picker.setTopLineColor(0x99FF0000);
- picker.setLabelTextColor(0xFFFF0000);
- picker.setDividerColor(0xFFFF0000);
- picker.setOnDateTimePickListener(new DateTimePicker.OnYearMonthDayTimePickListener() {
- @Override
- public void onDateTimePicked(String year, String month, String day, String hour, String minute) {
- showToast(year + "-" + month + "-" + day + " " + hour + ":" + minute);
- }
- });
- picker.show();
- }
-
-
- public void onYearMonthPicker(View view) {
- DatePicker picker = new DatePicker(this, DatePicker.YEAR_MONTH);
- picker.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
- picker.setWidth((int) (picker.getScreenWidthPixels() * 0.6));
- picker.setRangeStart(2016, 10, 14);
- picker.setRangeEnd(2020, 11, 11);
- picker.setSelectedItem(2017, 9);
- picker.setOnDatePickListener(new DatePicker.OnYearMonthPickListener() {
- @Override
- public void onDatePicked(String year, String month) {
- showToast(year + "-" + month);
- }
- });
- picker.show();
- }
-
- public void onMonthDayPicker(View view) {
- DatePicker picker = new DatePicker(this, DatePicker.MONTH_DAY);
- picker.setUseWeight(false);
- picker.setTextPadding(ConvertUtils.toPx(this, 15));//加宽显示项
- picker.setGravity(Gravity.CENTER);//弹框居中
- picker.setRangeStart(5, 1);
- picker.setRangeEnd(12, 31);
- picker.setSelectedItem(10, 14);
- picker.setOnDatePickListener(new DatePicker.OnMonthDayPickListener() {
- @Override
- public void onDatePicked(String month, String day) {
- showToast(month + "-" + day);
- }
- });
- picker.show();
- }
-
- public void onTimePicker(View view) {
- TimePicker picker = new TimePicker(this, TimePicker.HOUR_24);
- picker.setUseWeight(false);
- picker.setCycleDisable(false);
- picker.setRangeStart(0, 0);//00:00
- picker.setRangeEnd(23, 59);//23:59
- int currentHour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
- int currentMinute = Calendar.getInstance().get(Calendar.MINUTE);
- picker.setSelectedItem(currentHour, currentMinute);
- picker.setTopLineVisible(false);
- picker.setTextPadding(ConvertUtils.toPx(this, 15));
- picker.setOnTimePickListener(new TimePicker.OnTimePickListener() {
- @Override
- public void onTimePicked(String hour, String minute) {
- showToast(hour + ":" + minute);
- }
- });
- picker.show();
- }
-
- public void onOptionPicker(View view) {
- OptionPicker picker = new OptionPicker(this, new String[]{
- "第一项", "第二项", "第三项","第四项","第五项","第六项","第七项",
- "这是一个很长很长很长很长很长很长很长很长很长的很长很长的很长很长的项"
- });
- picker.setCanceledOnTouchOutside(false);
- picker.setDividerRatio(WheelView.DividerConfig.FILL);
- picker.setShadowColor(Color.RED, 40);
- picker.setSelectedIndex(1);
- picker.setCycleDisable(true);
- picker.setTextSize(11);
- picker.setOnOptionPickListener(new OptionPicker.OnOptionPickListener() {
- @Override
- public void onOptionPicked(int index, String item) {
- showToast("index=" + index + ", item=" + item);
- }
- });
- picker.show();
- }
-
- public void onSinglePicker(View view) {
- List data = new ArrayList<>();
- data.add(new GoodsCategory(1, "食品生鲜"));
- data.add(new GoodsCategory(2, "家用电器"));
- data.add(new GoodsCategory(3, "家居生活"));
- data.add(new GoodsCategory(4, "医疗保健"));
- data.add(new GoodsCategory(5, "酒水饮料"));
- data.add(new GoodsCategory(6, "图书音像"));
- SinglePicker picker = new SinglePicker<>(this, data);
- picker.setCanceledOnTouchOutside(false);
- picker.setSelectedIndex(1);
- picker.setCycleDisable(false);
- picker.setOnItemPickListener(new SinglePicker.OnItemPickListener() {
- @Override
- public void onItemPicked(int index, GoodsCategory item) {
- showToast("index=" + index + ", id=" + item.getId() + ", name=" + item.getName());
- }
- });
- picker.show();
- }
-
- public void onDoublePicker(View view) {
- final ArrayList firstData = new ArrayList<>();
- firstData.add("2017年5月2日星期二");
- firstData.add("2017年5月3日星期三");
- firstData.add("2017年5月4日星期四");
- firstData.add("2017年5月5日星期五");
- firstData.add("2017年5月6日星期六");
- final ArrayList secondData = new ArrayList<>();
- secondData.add("电动自行车");
- secondData.add("二轮摩托车");
- secondData.add("私家小汽车");
- secondData.add("公共交通汽车");
- final DoublePicker picker = new DoublePicker(this, firstData, secondData);
- picker.setDividerVisible(true);
- picker.setCycleDisable(false);
- picker.setSelectedIndex(0, 0);
- picker.setFirstLabel("于", null);
- picker.setSecondLabel("骑/乘", "出发");
- picker.setTextSize(12);
- picker.setContentPadding(15, 10);
- picker.setOnPickListener(new DoublePicker.OnPickListener() {
- @Override
- public void onPicked(int selectedFirstIndex, int selectedSecondIndex) {
- showToast(firstData.get(selectedFirstIndex) + " " + secondData.get(selectedSecondIndex));
- }
- });
- picker.show();
- }
-
- public void onBusinessTimePicker(View view) {
- final ArrayList hours = new ArrayList<>();
- for (int i = 0; i <= 23; i++) {
- hours.add(DateUtils.fillZero(i));
- }
- final ArrayList minutes = new ArrayList<>();
- minutes.add("00");
- minutes.add("15");
- minutes.add("30");
- DoublePicker picker = new DoublePicker(this, hours, minutes);
- picker.setCanceledOnTouchOutside(true);
- picker.setTopLineColor(0xFFFB2C3C);
- picker.setSubmitTextColor(0xFFFB2C3C);
- picker.setCancelTextColor(0xFFFB2C3C);
- picker.setLineSpaceMultiplier(2.2f);
- picker.setTextSize(15);
- picker.setTitleText("营业时间");
- picker.setContentPadding(10, 8);
- picker.setUseWeight(true);
- picker.setFirstLabel("", ":");
- picker.setSecondLabel("", "");
- picker.setOnPickListener(new DoublePicker.OnPickListener() {
- @Override
- public void onPicked(int selectedFirstIndex, int selectedSecondIndex) {
- showToast(hours.get(selectedFirstIndex) + ":" + minutes.get(selectedSecondIndex));
- }
- });
- picker.show();
- }
-
- public void onMultiplePicker(View view) {
- MultiplePicker picker = new MultiplePicker(this, new String[]{"穿青人", "少数民族", "已识别民族", "未定民族"});
- picker.setOnItemPickListener(new MultiplePicker.OnItemPickListener() {
- @Override
- public void onItemPicked(int count, List items) {
- showToast("已选" + count + "项:" + items);
- }
- });
- picker.show();
- }
-
- public void onLinkagePicker(View view) {
- //联动选择器的更多用法,可参见AddressPicker和CarNumberPicker
- LinkagePicker.DataProvider provider = new LinkagePicker.DataProvider() {
-
- @Override
- public boolean isOnlyTwo() {
- return true;
- }
-
- @NonNull
- @Override
- public List provideFirstData() {
- ArrayList firstList = new ArrayList<>();
- firstList.add("12");
- firstList.add("24");
- return firstList;
- }
-
- @NonNull
- @Override
- public List provideSecondData(int firstIndex) {
- ArrayList secondList = new ArrayList<>();
- for (int i = 1; i <= (firstIndex == 0 ? 12 : 24); i++) {
- String str = DateUtils.fillZero(i);
- if (firstIndex == 0) {
- str += "¥";
- } else {
- str += "$";
- }
- secondList.add(str);
- }
- return secondList;
- }
-
- @Nullable
- @Override
- public List provideThirdData(int firstIndex, int secondIndex) {
- return null;
- }
-
- };
- LinkagePicker picker = new LinkagePicker(this, provider);
- picker.setCycleDisable(true);
- picker.setUseWeight(true);
- picker.setLabel("小时制", "点");
- picker.setSelectedIndex(0, 8);
- //picker.setSelectedItem("12", "9");
- picker.setContentPadding(10, 0);
- picker.setOnStringPickListener(new LinkagePicker.OnStringPickListener() {
- @Override
- public void onPicked(String first, String second, String third) {
- showToast(first + "-" + second + "-" + third);
- }
- });
- picker.show();
- }
-
- public void onConstellationPicker(View view) {
- boolean isChinese = Locale.getDefault().getDisplayLanguage().contains("中文");
- OptionPicker picker = new OptionPicker(this,
- isChinese ? new String[]{
- "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座",
- "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座"
- } : new String[]{
- "Aquarius", "Pisces", "Aries", "Taurus", "Gemini", "Cancer",
- "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn"
- });
- picker.setCycleDisable(false);//不禁用循环
- picker.setTopBackgroundColor(0xFFEEEEEE);
- picker.setTopHeight(30);
- picker.setTopLineColor(0xFFEE0000);
- picker.setTopLineHeight(1);
- picker.setTitleText(isChinese ? "请选择" : "Please pick");
- picker.setTitleTextColor(0xFF999999);
- picker.setTitleTextSize(12);
- picker.setCancelTextColor(0xFFEE0000);
- picker.setCancelTextSize(13);
- picker.setSubmitTextColor(0xFFEE0000);
- picker.setSubmitTextSize(13);
- picker.setTextColor(0xFFEE0000, 0xFF999999);
- WheelView.DividerConfig config = new WheelView.DividerConfig();
- config.setColor(0xFFEE0000);//线颜色
- config.setAlpha(140);//线透明度
- config.setRatio((float) (1.0 / 8.0));//线比率
- picker.setDividerConfig(config);
- picker.setBackgroundColor(0xFFE1E1E1);
- //picker.setSelectedItem(isChinese ? "处女座" : "Virgo");
- picker.setSelectedIndex(7);
- picker.setCanceledOnTouchOutside(true);
- picker.setOnOptionPickListener(new OptionPicker.OnOptionPickListener() {
- @Override
- public void onOptionPicked(int index, String item) {
- showToast("index=" + index + ", item=" + item);
- }
- });
- picker.show();
- }
-
- public void onNumberPicker(View view) {
- NumberPicker picker = new NumberPicker(this);
- picker.setWidth(picker.getScreenWidthPixels() / 2);
- picker.setCycleDisable(false);
- picker.setDividerVisible(false);
- picker.setOffset(2);//偏移量
- picker.setRange(145, 200, 1);//数字范围
- picker.setSelectedItem(172);
- picker.setLabel("厘米");
- picker.setOnNumberPickListener(new NumberPicker.OnNumberPickListener() {
- @Override
- public void onNumberPicked(int index, Number item) {
- showToast("index=" + index + ", item=" + item.intValue());
- }
- });
- picker.show();
- }
-
- public void onAddressPicker(View view) {
- AddressPickTask task = new AddressPickTask(this);
- task.setHideProvince(false);
- task.setHideCounty(false);
- task.setCallback(new AddressPickTask.Callback() {
- @Override
- public void onAddressInitFailed() {
- showToast("数据初始化失败");
- }
-
- @Override
- public void onAddressPicked(Province province, City city, County county) {
- if (county == null) {
- showToast(province.getAreaName() + city.getAreaName());
- } else {
- showToast(province.getAreaName() + city.getAreaName() + county.getAreaName());
- }
- }
- });
- task.execute("贵州", "毕节", "纳雍");
- }
-
- public void onAddress2Picker(View view) {
- try {
- ArrayList data = new ArrayList<>();
- String json = ConvertUtils.toString(getAssets().open("city2.json"));
- data.addAll(JSON.parseArray(json, Province.class));
- AddressPicker picker = new AddressPicker(this, data);
- picker.setShadowVisible(true);
- picker.setTextSizeAutoFit(false);
- picker.setHideProvince(true);
- picker.setSelectedItem("贵州", "贵阳", "花溪");
- picker.setOnAddressPickListener(new AddressPicker.OnAddressPickListener() {
- @Override
- public void onAddressPicked(Province province, City city, County county) {
- showToast("province : " + province + ", city: " + city + ", county: " + county);
- }
- });
- picker.show();
- } catch (Exception e) {
- showToast(LogUtils.toStackTraceString(e));
- }
- }
-
- public void onAddress3Picker(View view) {
- AddressPickTask task = new AddressPickTask(this);
- task.setHideCounty(true);
- task.setCallback(new AddressPickTask.Callback() {
- @Override
- public void onAddressInitFailed() {
- showToast("数据初始化失败");
- }
-
- @Override
- public void onAddressPicked(Province province, City city, County county) {
- showToast(province.getAreaName() + " " + city.getAreaName());
- }
- });
- task.execute("四川", "阿坝");
- }
-
- public void onAddress4Picker(View view) {
- new AddressInitTask(this, new AddressInitTask.InitCallback() {
- @Override
- public void onDataInitFailure() {
- showToast("数据初始化失败");
- }
-
- @Override
- public void onDataInitSuccess(ArrayList provinces) {
- AddressPicker picker = new AddressPicker(activity, provinces);
- picker.setOnAddressPickListener(new AddressPicker.OnAddressPickListener() {
- @Override
- public void onAddressPicked(Province province, City city, County county) {
- String provinceName = province.getName();
- String cityName = "";
- if (city != null) {
- cityName = city.getName();
- //忽略直辖市的二级名称
- if (cityName.equals("市辖区") || cityName.equals("市") || cityName.equals("县")) {
- cityName = "";
- }
- }
- String countyName = "";
- if (county != null) {
- countyName = county.getName();
- }
- showToast(provinceName + " " + cityName + " " + countyName);
- }
- });
- picker.show();
- }
- }).execute();
- }
-
- public void onColorPicker(View view) {
- ColorPicker picker = new ColorPicker(this);
- picker.setInitColor(0xDD00DD);
- picker.setOnColorPickListener(new ColorPicker.OnColorPickListener() {
- @Override
- public void onColorPicked(int pickedColor) {
- showToast(ConvertUtils.toColorString(pickedColor));
- }
- });
- picker.show();
- }
-
- public void onFilePicker(View view) {
- FilePicker picker = new FilePicker(this, FilePicker.FILE);
- picker.setShowHideDir(false);
- //picker.setAllowExtensions(new String[]{".apk"});
- picker.setFileIcon(getResources().getDrawable(android.R.drawable.ic_menu_agenda));
- picker.setFolderIcon(getResources().getDrawable(android.R.drawable.ic_menu_upload_you_tube));
- //picker.setArrowIcon(getResources().getDrawable(android.R.drawable.arrow_down_float));
- picker.setOnFilePickListener(new FilePicker.OnFilePickListener() {
- @Override
- public void onFilePicked(String currentPath) {
- showToast(currentPath);
- }
- });
- picker.show();
- }
-
- public void onDirPicker(View view) {
- FilePicker picker = new FilePicker(this, FilePicker.DIRECTORY);
- picker.setRootPath(StorageUtils.getExternalRootPath() + "Download/");
- picker.setItemHeight(30);
- picker.setOnFilePickListener(new FilePicker.OnFilePickListener() {
- @Override
- public void onFilePicked(String currentPath) {
- showToast(currentPath);
- }
- });
- picker.show();
- }
-
- public void onContact(View view) {
- Intent intent = new Intent(Intent.ACTION_SENDTO);
- intent.setData(Uri.parse("mailto:liyujiang_tk@yeah.net"));
- intent.putExtra(Intent.EXTRA_CC, new String[]
- {"1032694760@qq.com"});
- intent.putExtra(Intent.EXTRA_EMAIL, "");
- intent.putExtra(Intent.EXTRA_TEXT, "欢迎提供意您的见或建议");
- startActivity(Intent.createChooser(intent, "选择邮件客户端"));
- }
-
-}
diff --git a/app/src/main/java/cn/qqtheme/androidpicker/MyPickerApp.java b/app/src/main/java/cn/qqtheme/androidpicker/MyPickerApp.java
deleted file mode 100644
index 72c2069e..00000000
--- a/app/src/main/java/cn/qqtheme/androidpicker/MyPickerApp.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package cn.qqtheme.androidpicker;
-
-import android.app.Application;
-
-import cn.qqtheme.framework.AppConfig;
-import cn.qqtheme.framework.util.LogUtils;
-
-/**
- * Author:李玉江[QQ:1032694760]
- * DateTime:2016/7/20 20:28
- * Builder:Android Studio
- */
-public class MyPickerApp extends Application {
-
- @Override
- public void onCreate() {
- super.onCreate();
- if (BuildConfig.DEBUG) {
- LogUtils.setIsDebug(true);
- } else {
- android.util.Log.d(AppConfig.DEBUG_TAG, "LogCat is disabled");
- }
- }
-
-}
diff --git a/app/src/main/java/cn/qqtheme/androidpicker/NestActivity.java b/app/src/main/java/cn/qqtheme/androidpicker/NestActivity.java
deleted file mode 100644
index 062126db..00000000
--- a/app/src/main/java/cn/qqtheme/androidpicker/NestActivity.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package cn.qqtheme.androidpicker;
-
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.util.Locale;
-
-import cn.qqtheme.framework.picker.CarNumberPicker;
-import cn.qqtheme.framework.picker.DatePicker;
-import cn.qqtheme.framework.picker.DateTimePicker;
-import cn.qqtheme.framework.util.ConvertUtils;
-import cn.qqtheme.framework.widget.WheelView;
-
-/**
- * 内嵌选择器
- *
- * Author:李玉江[QQ:1032694760]
- * DateTime:2016/12/16 00:42
- * Builder:Android Studio
- */
-public class NestActivity extends BaseActivity {
-
- @Override
- protected View getContentView() {
- return inflateView(R.layout.activity_nest);
- }
-
- @Override
- protected void setContentViewAfter(View contentView) {
- findViewById(R.id.nest_back).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- finish();
- }
- });
-
- final TextView textView = findView(R.id.wheelview_tips);
- WheelView wheelView = findView(R.id.wheelview_single);
- final String[] strings = {"少数民族", "贵州穿青人", "不在56个少数民族之列", "第57个民族"};
- wheelView.setItems(strings, 2);
- wheelView.setTextColor(0xFFFF00FF);
- wheelView.setTextSize(18);
- WheelView.DividerConfig config = new WheelView.DividerConfig();
- config.setRatio(1.0f / 10.0f);//线比率
- config.setColor(0xFFFF0000);//线颜色
- config.setAlpha(100);//线透明度
- config.setThick(ConvertUtils.toPx(this, 5));//线粗
- wheelView.setDividerConfig(config);
- wheelView.setOnItemSelectListener(new WheelView.OnItemSelectListener() {
- @Override
- public void onSelected(int index) {
- textView.setText(String.format(Locale.PRC, "index=%d,item=%s", index, strings[index]));
- }
- });
-
- LinearLayout layout = findView(R.id.wheelview_container);
- final CarNumberPicker carNumberPicker = new CarNumberPicker(this);
- carNumberPicker.setOffset(3);
- carNumberPicker.setUseWeight(true);
- carNumberPicker.setShadowColor(0xFFCCCCCC);
- carNumberPicker.setDividerRatio(WheelView.DividerConfig.FILL);
- carNumberPicker.setOnWheelLinkageListener(new CarNumberPicker.OnWheelLinkageListener() {
- @Override
- public void onLinkage(int firstIndex, int secondIndex, int thirdIndex) {
- textView.setText(String.format(Locale.PRC, "%s:%s", carNumberPicker.getSelectedFirstItem(), carNumberPicker.getSelectedSecondItem()));
- }
- });
- layout.addView(carNumberPicker.getContentView());
- final DatePicker datePicker = new DatePicker(this, DateTimePicker.YEAR_MONTH_DAY);
- datePicker.setOffset(4);
- datePicker.setOnWheelListener(new DatePicker.OnWheelListener() {
- @Override
- public void onYearWheeled(int index, String year) {
- textView.setText(String.format("%s年%s月%s日", year, datePicker.getSelectedMonth(), datePicker.getSelectedDay()));
- }
-
- @Override
- public void onMonthWheeled(int index, String month) {
- textView.setText(String.format("%s年%s月%s日", datePicker.getSelectedYear(), month, datePicker.getSelectedDay()));
- }
-
- @Override
- public void onDayWheeled(int index, String day) {
- textView.setText(String.format("%s年%s月%s日", datePicker.getSelectedYear(), datePicker.getSelectedMonth(), day));
- }
- });
- //得到选择器视图,可内嵌到其他视图容器,不需要调用show方法
- layout.addView(datePicker.getContentView());
- }
-
-}
diff --git a/app/src/main/java/cn/qqtheme/androidpicker/StatusBarUtil.java b/app/src/main/java/cn/qqtheme/androidpicker/StatusBarUtil.java
deleted file mode 100644
index 44c1f230..00000000
--- a/app/src/main/java/cn/qqtheme/androidpicker/StatusBarUtil.java
+++ /dev/null
@@ -1,542 +0,0 @@
-package cn.qqtheme.androidpicker;
-
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.content.Context;
-import android.graphics.Color;
-import android.os.Build;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.LinearLayout;
-
-import java.lang.reflect.Field;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.IdRes;
-import androidx.drawerlayout.widget.DrawerLayout;
-import cn.qqtheme.framework.util.LogUtils;
-
-/**
- * 状态栏(通知栏)工具类
- *
- * Created by liyujiang on 2017/4/19 15:35.
- */
-public class StatusBarUtil {
- private static final int DEFAULT_ALPHA = 0;
- @SuppressLint("ResourceType")
- @IdRes
- private static final int FAKE_STATUS_BAR_VIEW_ID = 0x20170419;
- @SuppressLint("ResourceType")
- @IdRes
- private static final int FAKE_TRANSLUCENT_VIEW_ID = 0x20170420;
- private static final int TAG_KEY_HAVE_SET_OFFSET = -123;
- private static int height = 0;
-
- /**
- * 是否设置了沉浸状态栏
- */
- public static boolean isImmersion(Activity activity) {
- return activity.findViewById(FAKE_STATUS_BAR_VIEW_ID) != null
- || activity.findViewById(FAKE_TRANSLUCENT_VIEW_ID) != null;
- }
-
- /**
- * 设置状态栏颜色
- *
- * @param activity 需要设置的 activity
- * @param color 状态栏颜色值
- */
- public static void setColor(Activity activity, @ColorInt int color) {
- setColor(activity, color, DEFAULT_ALPHA);
- }
-
- /**
- * 设置状态栏颜色
- *
- * @param activity 需要设置的activity
- * @param color 状态栏颜色值
- * @param statusBarAlpha 状态栏透明度
- */
-
- public static void setColor(Activity activity, @ColorInt int color, int statusBarAlpha) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- activity.getWindow().setStatusBarColor(calculateStatusColor(color, statusBarAlpha));
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
- View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID);
- if (fakeStatusBarView != null) {
- if (fakeStatusBarView.getVisibility() == View.GONE) {
- fakeStatusBarView.setVisibility(View.VISIBLE);
- }
- fakeStatusBarView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha));
- } else {
- decorView.addView(createStatusBarView(activity, color, statusBarAlpha));
- }
- setRootView(activity);
- }
- }
-
- /**
- * 设置状态栏纯色 不加半透明效果
- *
- * @param activity 需要设置的 activity
- * @param color 状态栏颜色值
- */
- public static void setColorNoTranslucent(Activity activity, @ColorInt int color) {
- setColor(activity, color, 0);
- }
-
- /**
- * 使状态栏半透明
- *
- * 适用于图片作为背景的界面,此时需要图片填充到状态栏
- *
- * @param activity 需要设置的activity
- */
- public static void setTranslucent(Activity activity) {
- setTranslucent(activity, DEFAULT_ALPHA);
- }
-
- /**
- * 使状态栏半透明
- *
- * 适用于图片作为背景的界面,此时需要图片填充到状态栏
- *
- * @param activity 需要设置的activity
- * @param statusBarAlpha 状态栏透明度
- */
- public static void setTranslucent(Activity activity, int statusBarAlpha) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- return;
- }
- setTransparent(activity);
- addTranslucentView(activity, statusBarAlpha);
- }
-
- /**
- * 针对根布局是 CoordinatorLayout, 使状态栏半透明
- *
- * 适用于图片作为背景的界面,此时需要图片填充到状态栏
- *
- * @param activity 需要设置的activity
- * @param statusBarAlpha 状态栏透明度
- */
- public static void setTranslucentForCoordinatorLayout(Activity activity, int statusBarAlpha) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- return;
- }
- setTransparentForWindow(activity);
- addTranslucentView(activity, statusBarAlpha);
- }
-
- /**
- * 设置状态栏全透明
- *
- * @param activity 需要设置的activity
- */
- public static void setTransparent(Activity activity) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- return;
- }
- setTransparentForWindow(activity);
- setRootView(activity);
- }
-
- /**
- * 为DrawerLayout 布局设置状态栏变色
- *
- * @param activity 需要设置的activity
- * @param drawerLayout DrawerLayout
- * @param color 状态栏颜色值
- */
- public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) {
- setColorForDrawerLayout(activity, drawerLayout, color, DEFAULT_ALPHA);
- }
-
- /**
- * 为DrawerLayout 布局设置状态栏颜色,纯色
- *
- * @param activity 需要设置的activity
- * @param drawerLayout DrawerLayout
- * @param color 状态栏颜色值
- */
- public static void setColorNoTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) {
- setColorForDrawerLayout(activity, drawerLayout, color, 0);
- }
-
- /**
- * 为DrawerLayout 布局设置状态栏变色
- *
- * @param activity 需要设置的activity
- * @param drawerLayout DrawerLayout
- * @param color 状态栏颜色值
- * @param statusBarAlpha 状态栏透明度
- */
- public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color,
- int statusBarAlpha) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- return;
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
- } else {
- activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- }
- // 生成一个状态栏大小的矩形
- // 添加 statusBarView 到布局中
- ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0);
- View fakeStatusBarView = contentLayout.findViewById(FAKE_STATUS_BAR_VIEW_ID);
- if (fakeStatusBarView != null) {
- if (fakeStatusBarView.getVisibility() == View.GONE) {
- fakeStatusBarView.setVisibility(View.VISIBLE);
- }
- fakeStatusBarView.setBackgroundColor(color);
- } else {
- contentLayout.addView(createStatusBarView(activity, color), 0);
- }
- // 内容布局不是 LinearLayout 时,设置padding top
- if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) {
- contentLayout.getChildAt(1)
- .setPadding(contentLayout.getPaddingLeft(), obtainHeight(activity) + contentLayout.getPaddingTop(),
- contentLayout.getPaddingRight(), contentLayout.getPaddingBottom());
- }
- // 设置属性
- setDrawerLayoutProperty(drawerLayout, contentLayout);
- addTranslucentView(activity, statusBarAlpha);
- }
-
- /**
- * 设置 DrawerLayout 属性
- *
- * @param drawerLayout DrawerLayout
- * @param drawerLayoutContentLayout DrawerLayout 的内容布局
- */
- private static void setDrawerLayoutProperty(DrawerLayout drawerLayout, ViewGroup drawerLayoutContentLayout) {
- ViewGroup drawer = (ViewGroup) drawerLayout.getChildAt(1);
- drawerLayout.setFitsSystemWindows(false);
- drawerLayoutContentLayout.setFitsSystemWindows(false);
- drawerLayoutContentLayout.setClipToPadding(true);
- drawer.setFitsSystemWindows(false);
- }
-
- /**
- * 为 DrawerLayout 布局设置状态栏透明
- *
- * @param activity 需要设置的activity
- * @param drawerLayout DrawerLayout
- */
- public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) {
- setTranslucentForDrawerLayout(activity, drawerLayout, DEFAULT_ALPHA);
- }
-
- /**
- * 为 DrawerLayout 布局设置状态栏透明
- *
- * @param activity 需要设置的activity
- * @param drawerLayout DrawerLayout
- */
- public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, int statusBarAlpha) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- return;
- }
- setTransparentForDrawerLayout(activity, drawerLayout);
- addTranslucentView(activity, statusBarAlpha);
- }
-
- /**
- * 为 DrawerLayout 布局设置状态栏透明
- *
- * @param activity 需要设置的activity
- * @param drawerLayout DrawerLayout
- */
- public static void setTransparentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- return;
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
- } else {
- activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- }
-
- ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0);
- // 内容布局不是 LinearLayout 时,设置padding top
- if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) {
- contentLayout.getChildAt(1).setPadding(0, obtainHeight(activity), 0, 0);
- }
-
- // 设置属性
- setDrawerLayoutProperty(drawerLayout, contentLayout);
- }
-
- /**
- * 为头部是 ImageView 的界面设置状态栏全透明
- *
- * @param activity 需要设置的activity
- * @param needOffsetView 需要向下偏移的 View
- */
- public static void setTransparentForImageView(Activity activity, View needOffsetView) {
- setTranslucentForImageView(activity, 0, needOffsetView);
- }
-
- /**
- * 为头部是 ImageView 的界面设置状态栏透明(使用默认透明度)
- *
- * @param activity 需要设置的activity
- * @param needOffsetView 需要向下偏移的 View
- */
- public static void setTranslucentForImageView(Activity activity, View needOffsetView) {
- setTranslucentForImageView(activity, DEFAULT_ALPHA, needOffsetView);
- }
-
- /**
- * 为头部是 ImageView 的界面设置状态栏透明
- *
- * @param activity 需要设置的activity
- * @param statusBarAlpha 状态栏透明度
- * @param needOffsetView 需要向下偏移的 View
- */
- public static void setTranslucentForImageView(Activity activity, int statusBarAlpha, View needOffsetView) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- return;
- }
- setTransparentForWindow(activity);
- addTranslucentView(activity, statusBarAlpha);
- if (needOffsetView != null) {
- Object haveSetOffset = needOffsetView.getTag(TAG_KEY_HAVE_SET_OFFSET);
- if (haveSetOffset != null && (Boolean) haveSetOffset) {
- return;
- }
- ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) needOffsetView.getLayoutParams();
- layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin + obtainHeight(activity),
- layoutParams.rightMargin, layoutParams.bottomMargin);
- needOffsetView.setTag(TAG_KEY_HAVE_SET_OFFSET, true);
- }
- }
-
- /**
- * 为 fragment 头部是 ImageView 的设置状态栏透明
- *
- * @param activity fragment 对应的 activity
- * @param needOffsetView 需要向下偏移的 View
- */
- public static void setTranslucentForImageViewInFragment(Activity activity, View needOffsetView) {
- setTranslucentForImageViewInFragment(activity, DEFAULT_ALPHA, needOffsetView);
- }
-
- /**
- * 为 fragment 头部是 ImageView 的设置状态栏透明
- *
- * @param activity fragment 对应的 activity
- * @param needOffsetView 需要向下偏移的 View
- */
- public static void setTransparentForImageViewInFragment(Activity activity, View needOffsetView) {
- setTranslucentForImageViewInFragment(activity, 0, needOffsetView);
- }
-
- /**
- * 为 fragment 头部是 ImageView 的设置状态栏透明
- *
- * @param activity fragment 对应的 activity
- * @param statusBarAlpha 状态栏透明度
- * @param needOffsetView 需要向下偏移的 View
- */
- public static void setTranslucentForImageViewInFragment(Activity activity, int statusBarAlpha, View needOffsetView) {
- setTranslucentForImageView(activity, statusBarAlpha, needOffsetView);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
- clearPreviousSetting(activity);
- }
- }
-
- /**
- * 隐藏伪状态栏 View
- *
- * @param activity 调用的 Activity
- */
- public static void hideFakeStatusBarView(Activity activity) {
- ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
- View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID);
- if (fakeStatusBarView != null) {
- fakeStatusBarView.setVisibility(View.GONE);
- }
- View fakeTranslucentView = decorView.findViewById(FAKE_TRANSLUCENT_VIEW_ID);
- if (fakeTranslucentView != null) {
- fakeTranslucentView.setVisibility(View.GONE);
- }
- }
-
- ///////////////////////////////////////////////////////////////////////////////////
-
- @TargetApi(Build.VERSION_CODES.KITKAT)
- private static void clearPreviousSetting(Activity activity) {
- ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
- View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID);
- if (fakeStatusBarView != null) {
- decorView.removeView(fakeStatusBarView);
- ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
- rootView.setPadding(0, 0, 0, 0);
- }
- }
-
- /**
- * 添加半透明矩形条
- *
- * @param activity 需要设置的 activity
- * @param statusBarAlpha 透明值
- */
- private static void addTranslucentView(Activity activity, int statusBarAlpha) {
- ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content);
- View fakeTranslucentView = contentView.findViewById(FAKE_TRANSLUCENT_VIEW_ID);
- if (fakeTranslucentView != null) {
- if (fakeTranslucentView.getVisibility() == View.GONE) {
- fakeTranslucentView.setVisibility(View.VISIBLE);
- }
- fakeTranslucentView.setBackgroundColor(Color.argb(statusBarAlpha, 0, 0, 0));
- } else {
- contentView.addView(createTranslucentStatusBarView(activity, statusBarAlpha));
- }
- }
-
- /**
- * 生成一个和状态栏大小相同的彩色矩形条
- *
- * @param activity 需要设置的 activity
- * @param color 状态栏颜色值
- * @return 状态栏矩形条
- */
- private static View createStatusBarView(Activity activity, @ColorInt int color) {
- return createStatusBarView(activity, color, 0);
- }
-
- /**
- * 生成一个和状态栏大小相同的半透明矩形条
- *
- * @param activity 需要设置的activity
- * @param color 状态栏颜色值
- * @param alpha 透明值
- * @return 状态栏矩形条
- */
- private static View createStatusBarView(Activity activity, @ColorInt int color, int alpha) {
- // 绘制一个和状态栏一样高的矩形
- View statusBarView = new View(activity);
- LinearLayout.LayoutParams params =
- new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, obtainHeight(activity));
- statusBarView.setLayoutParams(params);
- statusBarView.setBackgroundColor(calculateStatusColor(color, alpha));
- statusBarView.setId(FAKE_STATUS_BAR_VIEW_ID);
- return statusBarView;
- }
-
- /**
- * 设置根布局参数
- */
- private static void setRootView(Activity activity) {
- ViewGroup parent = (ViewGroup) activity.findViewById(android.R.id.content);
- for (int i = 0, count = parent.getChildCount(); i < count; i++) {
- View childView = parent.getChildAt(i);
- if (childView instanceof ViewGroup) {
- childView.setFitsSystemWindows(true);
- ((ViewGroup) childView).setClipToPadding(true);
- }
- }
- }
-
- /**
- * 设置透明
- */
- private static void setTransparentForWindow(Activity activity) {
- Window window = activity.getWindow();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- window.setStatusBarColor(Color.TRANSPARENT);
- window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- }
- }
-
- /**
- * 创建半透明矩形 View
- *
- * @param alpha 透明值
- * @return 半透明 View
- */
- private static View createTranslucentStatusBarView(Activity activity, int alpha) {
- // 绘制一个和状态栏一样高的矩形
- View statusBarView = new View(activity);
- LinearLayout.LayoutParams params =
- new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, obtainHeight(activity));
- statusBarView.setLayoutParams(params);
- statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0));
- statusBarView.setId(FAKE_TRANSLUCENT_VIEW_ID);
- return statusBarView;
- }
-
- /**
- * 获取状态栏的高,单位为px,参阅:http://blog.csdn.net/a_running_wolf/article/details/50477965
- */
- public static int obtainHeight(Context context) {
- if (height != 0) {
- return height;
- }
- int result = 80;// px
- try {
- int dimenResId = obtainDimenResId(context);
- result = context.getResources().getDimensionPixelSize(dimenResId);
- } catch (Exception e) {
- LogUtils.warn(e);
- if (context instanceof Activity) {
- android.graphics.Rect frame = new android.graphics.Rect();
- ((Activity) context).getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
- result = frame.top;
- }
- }
- LogUtils.verbose("status bar height: " + result + " px");
- height = result;
- return result;
- }
-
- private static int obtainDimenResId(Context ctx) throws ClassNotFoundException,
- NoSuchFieldException, IllegalAccessException, InstantiationException {
- LogUtils.verbose("will obtain status bar height from dimen resources");
- int resourceId = ctx.getResources().getIdentifier("status_bar_height", "dimen", "android");
- if (resourceId > 0) {
- return resourceId;
- }
- LogUtils.verbose("will obtain status bar height from R$dimen class");
- Class clazz = Class.forName("com.android.internal.R$dimen");
- Field field = clazz.getField("status_bar_height");
- Object object = field.get(clazz.newInstance());
- return Integer.parseInt(object.toString());
- }
-
- /**
- * 计算状态栏颜色
- *
- * @param color color值
- * @param alpha alpha值
- * @return 最终的状态栏颜色
- */
- private static int calculateStatusColor(@ColorInt int color, int alpha) {
- if (alpha == 0) {
- return color;
- }
- float a = 1 - alpha / 255f;
- int red = color >> 16 & 0xff;
- int green = color >> 8 & 0xff;
- int blue = color & 0xff;
- red = (int) (red * a + 0.5);
- green = (int) (green * a + 0.5);
- blue = (int) (blue * a + 0.5);
- return 0xff << 24 | red << 16 | green << 8 | blue;
- }
-}
diff --git a/app/src/main/res/layout/activity_nest.xml b/app/src/main/res/layout/activity_nest.xml
deleted file mode 100644
index 2fb08bcc..00000000
--- a/app/src/main/res/layout/activity_nest.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/picker_footer.xml b/app/src/main/res/layout/picker_footer.xml
deleted file mode 100644
index d9fd2b80..00000000
--- a/app/src/main/res/layout/picker_footer.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/picker_header.xml b/app/src/main/res/layout/picker_header.xml
deleted file mode 100644
index 23fd9782..00000000
--- a/app/src/main/res/layout/picker_header.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
deleted file mode 100644
index 14602804..00000000
--- a/app/src/main/res/values/ids.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/basepicker/src/main/java/com/github/gzuliyujiang/basepicker/BottomPicker.java b/basepicker/src/main/java/com/github/gzuliyujiang/basepicker/BottomPicker.java
index fd079ac5..d875f8ab 100644
--- a/basepicker/src/main/java/com/github/gzuliyujiang/basepicker/BottomPicker.java
+++ b/basepicker/src/main/java/com/github/gzuliyujiang/basepicker/BottomPicker.java
@@ -87,7 +87,9 @@ private void init(Activity activity) {
window.getDecorView().setPadding(0, 0, 0, 0);
}
// 调用create或show才能触发onCreate
- create();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ create();
+ }
}
protected void onInit(@NonNull Context context) {
@@ -140,7 +142,9 @@ private void addMaskView() {
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
params.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
- params.flags = WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ params.flags = WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
params.format = PixelFormat.TRANSLUCENT;
params.token = activity.getWindow().getDecorView().getWindowToken();
diff --git a/gradle/config.gradle b/gradle/config.gradle
index ee8edd26..c7eb7a9c 100644
--- a/gradle/config.gradle
+++ b/gradle/config.gradle
@@ -14,7 +14,7 @@
ext {
//Android版本(4.4-19、5.0-21、5.1-22、6.0-23、7.0-24、8.0-26、9.0-28、10-29、11-30)
- minSdkVersion = 21
+ minSdkVersion = 19
targetSdkVersion = 29
compileSdkVersion = 30
buildToolsVersion = '30.0.3'
diff --git a/screenshots/address.gif b/screenshots/address.gif
deleted file mode 100644
index ab9e7d41cc6abf6f24f21493157e0eea856dccd5..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 571923
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