Der Screenshot des Effekts sieht wie folgt aus:
erreichen:
Führen Sie zunächst abhängige Bibliotheken und JAR-Dateien ein (siehe GitHub für JAR-Dateien ):
dependencies {
···
//汉字转拼音
implementation files('libs\\pinyin4j-2.5.0.jar')
//recyclerview
implementation 'androidx.recyclerview:recyclerview:1.1.0'
//BaseRecyclerViewAdapterHelper
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.34'
//glide
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
}
Da Lambda-Ausdrücke und viewBinding verwendet werden, muss build.gradle Folgendes einführen:
android {
···
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
viewBinding {
enabled true
}
}
Homepage-Layout activity_main:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorWhite"
android:orientation="vertical">
<TextView
android:id="@+id/tv_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="10dp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/dialog"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:background="@drawable/sidebar_background"
android:gravity="center"
android:textColor="@color/colorWhite"
android:textSize="30dp"
android:visibility="invisible" />
<com.example.appliedstatistics.view.SideBar
android:id="@+id/sideBar"
android:layout_width="30dp"
android:layout_height="500dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" />
</RelativeLayout>
</LinearLayout>
Benutzerdefinierte Seitenleiste Seitenleiste:
/**
* 自定义侧边栏
*/
public class SideBar extends View {
// 触摸事件
private OnTouchingLetterChangedListener onTouchingLetterChangedListener;
public static String[] b = {"#", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z"};
private int choose = -1;
private Paint paint = new Paint();
private TextView mTextDialog;
/**
* 为SideBar设置显示字母的TextView
*
* @param textDialog
*/
public void setTextView(TextView textDialog) {
this.mTextDialog = textDialog;
}
public SideBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public SideBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SideBar(Context context) {
super(context);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();
int width = getWidth();
int singleHeight = height / b.length;// 获取每一个字母的高度
for (int i = 0; i < b.length; i++) {
paint.setColor(Color.rgb(33, 65, 98));
// paint.setColor(Color.WHITE);
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setAntiAlias(true);
paint.setTextSize(30);
if (i == choose) {// 选中的状态
paint.setColor(Color.parseColor("#3399ff"));
paint.setFakeBoldText(true);
}
// x坐标等于中间-字符串宽度的一半
float xPos = width / 2 - paint.measureText(b[i]) / 2;
float yPos = singleHeight * i + singleHeight;
canvas.drawText(b[i], xPos, yPos, paint);
paint.reset();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float y = event.getY();
final int oldChoose = choose;
final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
final int c = (int) (y / getHeight() * b.length);// 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数
switch (action) {
case MotionEvent.ACTION_UP:
setBackground(new ColorDrawable(0x00000000));
choose = -1;//
invalidate();
if (mTextDialog != null) {
mTextDialog.setVisibility(View.INVISIBLE);
}
break;
default:
setBackgroundResource(R.drawable.sidebar_background);
if (oldChoose != c) {
if (c >= 0 && c < b.length) {
if (listener != null) {
listener.onTouchingLetterChanged(b[c]);
}
if (mTextDialog != null) {
mTextDialog.setText(b[c]);
mTextDialog.setVisibility(View.VISIBLE);
}
choose = c;
invalidate();
}
}
break;
}
return true;
}
/**
* 触摸事件
*
* @param onTouchingLetterChangedListener
*/
public void setOnTouchingLetterChangedListener(
OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
}
/**
* @author coder
*/
public interface OnTouchingLetterChangedListener {
void onTouchingLetterChanged(String s);
}
}
Informationen zu installierten Apps abrufen:
/**
* 获取已安装应用信息
*/
@SuppressLint("SetTextI18n")
private void getAppInfo() {
@SuppressLint("WrongConstant") List<ApplicationInfo> apps = getPackageManager().getInstalledApplications(PackageManager.GET_SIGNATURES);
infos = new ArrayList<>();
for (ApplicationInfo info : apps) {
if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
// 非系统应用
AppInfo appInfo = new AppInfo();
appInfo.name = info.loadLabel(getPackageManager()).toString();
appInfo.icon = info.loadIcon(getPackageManager());
appInfo.path = info.sourceDir;
appInfo.packageName = info.packageName;
//汉字转换成拼音
String pinyin = PinyinUtils.getPingYin(info.loadLabel(getPackageManager()).toString());
String sortString = pinyin.substring(0, 1).toUpperCase();
// 正则表达式,判断首字母是否是英文字母
if (sortString.matches("[A-Z]")) {
appInfo.setLetters(sortString.toUpperCase());
} else {
appInfo.setLetters("#");
}
infos.add(appInfo);
} else {
//全部系统应用
}
}
binding.tvNum.setText("您一共安装了" + infos.size() + "个应用程序");
}
Werkzeug:
/**
* 侧边栏特殊符号显示处理
*/
public class PinyinComparator implements Comparator<AppInfo> {
@Override
public int compare(AppInfo o1, AppInfo o2) {
if (o1.getLetters().equals("@") || o2.getLetters().equals("#")) {
return 1;
} else if (o1.getLetters().equals("#") || o2.getLetters().equals("@")) {
return -1;
} else {
return o1.getLetters().compareTo(o2.getLetters());
}
}
}
public class PinyinUtils {
/**
* 获取拼音
*
* @param inputString
* @return
*/
public static String getPingYin(String inputString) {
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
format.setVCharType(HanyuPinyinVCharType.WITH_V);
char[] input = inputString.trim().toCharArray();
String output = "";
try {
for (char curChar : input) {
if (Character.toString(curChar).matches("[\\u4E00-\\u9FA5]+")) {
String[] temp = PinyinHelper.toHanyuPinyinStringArray(curChar, format);
output += temp[0];
} else
output += Character.toString(curChar);
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
return output;
}
/**
* 获取第一个字的拼音首字母
*
* @param chinese
* @return
*/
public static String getFirstSpell(String chinese) {
StringBuffer pinYinBF = new StringBuffer();
char[] arr = chinese.toCharArray();
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (char curChar : arr) {
if (curChar > 128) {
try {
String[] temp = PinyinHelper.toHanyuPinyinStringArray(curChar, defaultFormat);
if (temp != null) {
pinYinBF.append(temp[0].charAt(0));
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
} else {
pinYinBF.append(curChar);
}
}
return pinYinBF.toString().replaceAll("\\W", "").trim();
}
}
Bean-Klassen- und Listenadapter:
public class AppInfo {
//序列号
private static final long serialVersionUID = -6660233212727684115L;
//名称
public String name;
//路径
public String path;
//图标
public Drawable icon;
//包名
public String packageName;
//显示拼音的首字母
private String letters;
public static long getSerialVersionUID() {
return serialVersionUID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Drawable getIcon() {
return icon;
}
public void setIcon(Drawable icon) {
this.icon = icon;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public String getLetters() {
return letters;
}
public void setLetters(String letters) {
this.letters = letters;
}
}
public class SortAdapter extends RecyclerView.Adapter<SortAdapter.ViewHolder> {
private LayoutInflater mInflater;
private List<AppInfo> mData;
private Context mContext;
public SortAdapter(Context context, List<AppInfo> data) {
mInflater = LayoutInflater.from(context);
mData = data;
this.mContext = context;
}
@Override
public SortAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ItemAppInfoBinding binding = ItemAppInfoBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new SortAdapter.ViewHolder(binding);
}
@Override
public void onBindViewHolder(final SortAdapter.ViewHolder holder, final int position) {
ItemAppInfoBinding bind = ItemAppInfoBinding.bind(holder.itemView);
if (mOnItemClickListener != null) {
holder.itemView.setOnClickListener(v -> mOnItemClickListener.onItemClick(holder.itemView, position));
}
Glide.with(mContext).load(mData.get(position).icon).into(bind.ivIcon);
bind.tvName.setText(mData.get(position).name);
bind.ll.setOnClickListener(v -> {
//通过包名打开应用
PackageManager packageManager = mContext.getPackageManager();
Intent intent = packageManager.getLaunchIntentForPackage(mData.get(position).packageName);
mContext.startActivity(intent);
});
}
@Override
public int getItemCount() {
return mData.size();
}
//**********************itemClick************************
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
//**************************************************************
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(ItemAppInfoBinding itemView) {
super(itemView.getRoot());
}
}
/**
* 提供给Activity刷新数据
*
* @param list
*/
public void updateList(List<AppInfo> list) {
this.mData = list;
notifyDataSetChanged();
}
public Object getItem(int position) {
return mData.get(position);
}
/**
* 根据ListView的当前位置获取分类的首字母的char ascii值
*/
public int getSectionForPosition(int position) {
return mData.get(position).getLetters().charAt(0);
}
/**
* 根据分类的首字母的Char ascii值获取其第一次出现该首字母的位置
*/
public int getPositionForSection(int section) {
for (int i = 0; i < getItemCount(); i++) {
String sortStr = mData.get(i).getLetters();
char firstChar = sortStr.toUpperCase().charAt(0);
if (firstChar == section) {
return i;
}
}
return -1;
}
}
Berühren Sie Ereignisse zum Sortieren und Seitenleisten:
// 根据a-z进行排序源数据
Collections.sort(infos, pinyinComparator);
manager = new GridLayoutManager(this, 4);//列数设置
manager.setOrientation(LinearLayoutManager.VERTICAL);
binding.recyclerView.setLayoutManager(manager);
adapter = new SortAdapter(this, infos);
binding.recyclerView.setAdapter(adapter);
binding.sideBar.setTextView(binding.dialog);
//设置右侧SideBar触摸监听
binding.sideBar.setOnTouchingLetterChangedListener(s -> {
//该字母首次出现的位置
int position = adapter.getPositionForSection(s.charAt(0));
if (position != -1) {
manager.scrollToPositionWithOffset(position, 0);
}
});
Adapterelement-Layout item_app_info und benutzerdefinierter Hintergrund sidebar_background:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ripple_water"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:layout_marginBottom="10dp"
android:text="应用名称"
android:textColor="@color/colorBlack"
android:textSize="14sp" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#14000000" />
<corners android:radius="10dp" />
</shape>
Water Ripple Click Effect Background ripple_water benötigt v21-Unterstützung, also legen Sie es unter die Datei drawable-v21:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#d7d7d7"><!--点击时波纹的颜色-->
<item android:drawable="@android:color/white" /> <!--未点击时控件的背景(可以是图片,可以是颜色,也可以是drawable里的xml背景(比如圆角))-->
</ripple>
Codedetailreferenz: GitHub