Android development installed application statistics

The screenshot of the effect is as follows:

 accomplish:

 First introduce dependent libraries and jar files (see GitHub for jar files ):

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'
}

 Because lambda expressions and viewBinding are used, build.gradle needs to be introduced:

android {

    ···

    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }

    viewBinding {
        enabled true
    }
}

 Home page 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>

Custom sidebar SideBar:

/**
 * 自定义侧边栏
 */
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);
    }
}

Get information about installed apps:

    /**
     * 获取已安装应用信息
     */
    @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() + "个应用程序");
    }

Tools:

/**
 * 侧边栏特殊符号显示处理
 */
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 class and list adapter:

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;
    }
}

Touch events for sorting and sidebars:

        // 根据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);
            }
        });

Adapter item layout item_app_info and custom background 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 needs v21 support, so put it in the file 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>

Code details reference: GitHub

Guess you like

Origin blog.csdn.net/juer2017/article/details/120130863