commit 04c1a63caa523e78fe00a14cc11b7b967a530c9f Author: muqing <1966944300@qq.com> Date: Mon Jul 22 20:54:14 2024 +0800 git fetch origin diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70cf89a --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +*.apk \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/dictionaries/19669.xml b/.idea/dictionaries/19669.xml new file mode 100644 index 0000000..6dac566 --- /dev/null +++ b/.idea/dictionaries/19669.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..d170b6e --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..2823ce9 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,27 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..1b261c7 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..2b8a50f --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..932c4df --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,52 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab0f144 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +## 云音乐是什么? +一个对接网易云的音乐播放器 +* 内置集成作者自己写的歌词Lrc组件支持单行歌词和多行歌词支持悬浮窗歌词。 +* 内置适配Android13的通知栏 (不完善请大佬请教) +* 对接了网易云的歌单,歌曲,搜索,二维码登录等其余功能。 +* main.java-内api变量是网易云SDK后台服务器地址(可变更) +* http 是我服务器php接口的地址用于软件更新用或者其他功能(个根据需要做出必须的更改或者删除) + +## 进度 +* UI + * 歌单 + * 音乐播放选择列表 + * 音乐播放器控制界面 + * 登陆 + * 设置 + * 侧滑栏内小功能 +* 代码 + * 适配主题和UI美化 + * 维护接口(不完善) + * 蓝牙功能 + * 通知栏控制 + * 悬浮歌词功能 + * 基本播放器功能(控制暂停 上下曲 播放歌单操作) + + +## 截图 +Screenshot_20240224_154259_com.muqingbfq.jpg +Screenshot_20240224_154302_com.muqingbfq.jpg +Screenshot_20240224_154306_com.muqingbfq.jpg + +## 在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流 + * QQ:1966944300 + +## 后台 + * Github: [网易云音乐 API](https://github.com/Binaryify/NeteaseCloudMusicApi) + +## 关于 +在兴趣的驱动下,写一个`免费`的东西,有欣喜,也还有汗水,希望你喜欢我的作品,同时也能支持一下。 + + +## 修改JAR的包 +```javascript +[//]: # (主要修改内容MD3化) +com.github.QuadFlask:colorpicker:0.0.15 +[//]: # (歌词做全局变量给悬浮窗歌词) +'com.github.wangchenyan:lrcview:2.2.1' +... \ No newline at end of file diff --git a/StatusBarUtil/.gitignore b/StatusBarUtil/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/StatusBarUtil/.gitignore @@ -0,0 +1 @@ +/build diff --git a/StatusBarUtil/build.gradle b/StatusBarUtil/build.gradle new file mode 100644 index 0000000..a40a6c3 --- /dev/null +++ b/StatusBarUtil/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.library' +version = "1.5.1" +android { + namespace 'com.jaeger.library' + compileSdk 34 + resourcePrefix "statusbarutil_" + defaultConfig { + minSdkVersion 23 + //noinspection ExpiredTargetSdkVersion + targetSdk 31 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' +} \ No newline at end of file diff --git a/StatusBarUtil/src/main/AndroidManifest.xml b/StatusBarUtil/src/main/AndroidManifest.xml new file mode 100644 index 0000000..758b0af --- /dev/null +++ b/StatusBarUtil/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/StatusBarUtil/src/main/java/com/jaeger/library/StatusBarUtil.java b/StatusBarUtil/src/main/java/com/jaeger/library/StatusBarUtil.java new file mode 100644 index 0000000..aa0fe40 --- /dev/null +++ b/StatusBarUtil/src/main/java/com/jaeger/library/StatusBarUtil.java @@ -0,0 +1,726 @@ +package com.jaeger.library; + +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 androidx.annotation.ColorInt; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.drawerlayout.widget.DrawerLayout; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Created by Jaeger on 16/2/14. + *

+ * Email: chjie.jaeger@gmail.com + * GitHub: ... + */ +public class StatusBarUtil { + + public static final int DEFAULT_STATUS_BAR_ALPHA = 112; + private static final int FAKE_STATUS_BAR_VIEW_ID = R.id.statusbarutil_fake_status_bar_view; + private static final int FAKE_TRANSLUCENT_VIEW_ID = R.id.statusbarutil_translucent_view; + private static final int TAG_KEY_HAVE_SET_OFFSET = -123; + + /** + * 设置状态栏颜色 + * + * @param activity 需要设置的 activity + * @param color 状态栏颜色值 + */ + public static void setColor(Activity activity, @ColorInt int color) { + setColor(activity, color, DEFAULT_STATUS_BAR_ALPHA); + } + + /** + * 设置状态栏颜色 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @param statusBarAlpha 状态栏透明度 + */ + + public static void setColor(Activity activity, @ColorInt int color, @IntRange(from = 0, to = 255) 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 setColorForSwipeBack(Activity activity, int color) { + setColorForSwipeBack(activity, color, DEFAULT_STATUS_BAR_ALPHA); + } + + /** + * 为滑动返回界面设置状态栏颜色 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @param statusBarAlpha 状态栏透明度 + */ + public static void setColorForSwipeBack(Activity activity, @ColorInt int color, + @IntRange(from = 0, to = 255) int statusBarAlpha) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + + ViewGroup contentView = ((ViewGroup) activity.findViewById(android.R.id.content)); + View rootView = contentView.getChildAt(0); + int statusBarHeight = getStatusBarHeight(activity); + if (rootView instanceof CoordinatorLayout) { + final CoordinatorLayout coordinatorLayout = (CoordinatorLayout) rootView; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + coordinatorLayout.setFitsSystemWindows(false); + contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); + boolean isNeedRequestLayout = contentView.getPaddingTop() < statusBarHeight; + if (isNeedRequestLayout) { + contentView.setPadding(0, statusBarHeight, 0, 0); + coordinatorLayout.post(coordinatorLayout::requestLayout); + } + } else { + coordinatorLayout.setStatusBarBackgroundColor(calculateStatusColor(color, statusBarAlpha)); + } + } else { + contentView.setPadding(0, statusBarHeight, 0, 0); + contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); + } + setTransparentForWindow(activity); + } + } + + /** + * 设置状态栏纯色 不加半透明效果 + * + * @param activity 需要设置的 activity + * @param color 状态栏颜色值 + */ + public static void setColorNoTranslucent(Activity activity, @ColorInt int color) { + setColor(activity, color, 0); + } + + /** + * 设置状态栏颜色(5.0以下无半透明效果,不建议使用) + * + * @param activity 需要设置的 activity + * @param color 状态栏颜色值 + */ + @Deprecated + public static void setColorDiff(Activity activity, @ColorInt int color) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + transparentStatusBar(activity); + ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); + // 移除半透明矩形,以免叠加 + View fakeStatusBarView = contentView.findViewById(FAKE_STATUS_BAR_VIEW_ID); + if (fakeStatusBarView != null) { + if (fakeStatusBarView.getVisibility() == View.GONE) { + fakeStatusBarView.setVisibility(View.VISIBLE); + } + fakeStatusBarView.setBackgroundColor(color); + } else { + contentView.addView(createStatusBarView(activity, color)); + } + setRootView(activity); + } + + /** + * 使状态栏半透明 + *

+ * 适用于图片作为背景的界面,此时需要图片填充到状态栏 + * + * @param activity 需要设置的activity + */ + public static void setTranslucent(Activity activity) { + setTranslucent(activity, DEFAULT_STATUS_BAR_ALPHA); + } + + /** + * 使状态栏半透明 + *

+ * 适用于图片作为背景的界面,此时需要图片填充到状态栏 + * + * @param activity 需要设置的activity + * @param statusBarAlpha 状态栏透明度 + */ + public static void setTranslucent(Activity activity, @IntRange(from = 0, to = 255) 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, @IntRange(from = 0, to = 255) int statusBarAlpha) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + transparentStatusBar(activity); + addTranslucentView(activity, statusBarAlpha); + } + + /** + * 设置状态栏全透明 + * + * @param activity 需要设置的activity + */ + public static void setTransparent(Activity activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + transparentStatusBar(activity); + setRootView(activity); + } + + /** + * 使状态栏透明(5.0以上半透明效果,不建议使用) + *

+ * 适用于图片作为背景的界面,此时需要图片填充到状态栏 + * + * @param activity 需要设置的activity + */ + @Deprecated + public static void setTranslucentDiff(Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // 设置状态栏透明 + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + 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_STATUS_BAR_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, + @IntRange(from = 0, to = 255) 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(), getStatusBarHeight(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 布局设置状态栏变色(5.0以下无半透明效果,不建议使用) + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + * @param color 状态栏颜色值 + */ + @Deprecated + public static void setColorForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + // 生成一个状态栏大小的矩形 + 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(calculateStatusColor(color, DEFAULT_STATUS_BAR_ALPHA)); + } else { + // 添加 statusBarView 到布局中 + contentLayout.addView(createStatusBarView(activity, color), 0); + } + // 内容布局不是 LinearLayout 时,设置padding top + if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { + contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0); + } + // 设置属性 + setDrawerLayoutProperty(drawerLayout, contentLayout); + } + } + + /** + * 为 DrawerLayout 布局设置状态栏透明 + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + */ + public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) { + setTranslucentForDrawerLayout(activity, drawerLayout, DEFAULT_STATUS_BAR_ALPHA); + } + + /** + * 为 DrawerLayout 布局设置状态栏透明 + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + */ + public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, + @IntRange(from = 0, to = 255) 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, getStatusBarHeight(activity), 0, 0); + } + + // 设置属性 + setDrawerLayoutProperty(drawerLayout, contentLayout); + } + + /** + * 为 DrawerLayout 布局设置状态栏透明(5.0以上半透明效果,不建议使用) + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + */ + @Deprecated + public static void setTranslucentForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // 设置状态栏透明 + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + // 设置内容布局属性 + ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); + contentLayout.setFitsSystemWindows(true); + contentLayout.setClipToPadding(true); + // 设置抽屉布局属性 + ViewGroup vg = (ViewGroup) drawerLayout.getChildAt(1); + vg.setFitsSystemWindows(false); + // 设置 DrawerLayout 属性 + drawerLayout.setFitsSystemWindows(false); + } + } + + /** + * 为头部是 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_STATUS_BAR_ALPHA, needOffsetView); + } + + /** + * 为头部是 ImageView 的界面设置状态栏透明 + * + * @param activity 需要设置的activity + * @param statusBarAlpha 状态栏透明度 + * @param needOffsetView 需要向下偏移的 View + */ + public static void setTranslucentForImageView(Activity activity, @IntRange(from = 0, to = 255) 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 + getStatusBarHeight(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_STATUS_BAR_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, @IntRange(from = 0, to = 255) 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.M) + public static void setLightMode(Activity activity) { + setMIUIStatusBarDarkIcon(activity, true); + setMeizuStatusBarDarkIcon(activity, true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + } + + @TargetApi(Build.VERSION_CODES.M) + public static void setDarkMode(Activity activity) { + setMIUIStatusBarDarkIcon(activity, false); + setMeizuStatusBarDarkIcon(activity, false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + } + + /** + * 修改 MIUI V6 以上状态栏颜色 + */ + private static void setMIUIStatusBarDarkIcon(@NonNull Activity activity, boolean darkIcon) { + Class clazz = activity.getWindow().getClass(); + try { + Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); + Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); + int darkModeFlag = field.getInt(layoutParams); + Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); + extraFlagField.invoke(activity.getWindow(), darkIcon ? darkModeFlag : 0, darkModeFlag); + } catch (Exception e) { + //e.printStackTrace(); + } + } + + /** + * 修改魅族状态栏字体颜色 Flyme 4.0 + */ + private static void setMeizuStatusBarDarkIcon(@NonNull Activity activity, boolean darkIcon) { + try { + WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); + Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); + Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags"); + darkFlag.setAccessible(true); + meizuFlags.setAccessible(true); + int bit = darkFlag.getInt(null); + int value = meizuFlags.getInt(lp); + if (darkIcon) { + value |= bit; + } else { + value &= ~bit; + } + meizuFlags.setInt(lp, value); + activity.getWindow().setAttributes(lp); + } catch (Exception e) { + //e.printStackTrace(); + } + } + + /////////////////////////////////////////////////////////////////////////////////// + + @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, @IntRange(from = 0, to = 255) 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, getStatusBarHeight(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) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + activity.getWindow() + .getDecorView() + .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + activity.getWindow() + .setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + } + + /** + * 使状态栏透明 + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + private static void transparentStatusBar(Activity activity) { + 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().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + } else { + activity.getWindow().addFlags(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, getStatusBarHeight(activity)); + statusBarView.setLayoutParams(params); + statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0)); + statusBarView.setId(FAKE_TRANSLUCENT_VIEW_ID); + return statusBarView; + } + + /** + * 获取状态栏高度 + * + * @param context context + * @return 状态栏高度 + */ + private static int getStatusBarHeight(Context context) { + // 获得状态栏高度 + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + return context.getResources().getDimensionPixelSize(resourceId); + } + + /** + * 计算状态栏颜色 + * + * @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/StatusBarUtil/src/main/res/values/ids.xml b/StatusBarUtil/src/main/res/values/ids.xml new file mode 100644 index 0000000..faad20c --- /dev/null +++ b/StatusBarUtil/src/main/res/values/ids.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..67b19d6 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,78 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} +android { + signingConfigs { + debug { + storeFile file('../muqing.jks') + storePassword 'muqing153' + keyAlias 'muqing' + keyPassword 'muqing153' + } + } + namespace 'com.muqingbfq' + compileSdk 33 + defaultConfig { + applicationId "com.muqingbfq" + minSdk 23 + //noinspection ExpiredTargetSdkVersion,OldTargetApi + targetSdk 33 + versionCode 1 + versionName "2.6.0" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildTypes { + release { +// shrinkResources true + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + android.applicationVariants.configureEach { + variant -> + variant.outputs.configureEach { + //在这里修改apk文件名 + outputFileName = "Cloud_music-${variant.name}-v${variant.versionName}.apk" + } + } + kotlinOptions { + jvmTarget = '1.8' + } + viewBinding { + enabled = true + } +} +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + + implementation 'com.google.code.gson:gson:2.9.1' + + implementation 'com.squareup.okhttp3:okhttp:4.11.0' + //图片处理 + implementation 'com.github.bumptech.glide:glide:4.16.0' + + implementation "androidx.palette:palette-ktx:1.0.0" +// 废弃的歌词组件 +// implementation 'com.github.wangchenyan:lrcview:2.2.1' + implementation 'com.google.android.flexbox:flexbox:3.0.0' + + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + +//修改音乐标签库 + implementation 'com.mpatric:mp3agic:0.9.1' + +// 沉浸式状态栏 + implementation project(path: ':StatusBarUtil') +// api project(path: ':lrcview') + //歌词组件库 + api "com.github.cy745:EaseView:e11c3208a9" + api "androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03" +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3d281b3 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/about.html b/app/src/main/assets/about.html new file mode 100644 index 0000000..d2b563a --- /dev/null +++ b/app/src/main/assets/about.html @@ -0,0 +1,56 @@ +

开源在线音乐播放器项目

+

该项目由作者MUQING编写,采用了 JAVA 设计语言,该应用对接了网易云音乐API,为用户提供了一系列功能丰富的服务:

+
    +
  1. 音乐搜索与发现: + +
  2. +
  3. 在线播放与下载: + +
  4. +
  5. 个性化歌单与收藏: + +
  6. +
  7. 歌词显示与同步: + +
  8. +
  9. 账号绑定与同步: + +
  10. + + + +
  11. 智能推荐系统: + +
  12. +
  13. 后台播放与控制: + +
  14. +
+

通过这个开源项目,开发者学习如何对接第三方API实现音乐播放功能,还能了解各种小功能和细节的实现。

\ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/ColorCircle.java b/app/src/main/java/com/colorpicker/ColorCircle.java new file mode 100644 index 0000000..f08d440 --- /dev/null +++ b/app/src/main/java/com/colorpicker/ColorCircle.java @@ -0,0 +1,54 @@ +package com.colorpicker; + +import android.graphics.Color; + +public class ColorCircle { + private float x, y; + private float[] hsv = new float[3]; + private float[] hsvClone; + private int color; + + public ColorCircle(float x, float y, float[] hsv) { + set(x, y, hsv); + } + + public double sqDist(float x, float y) { + double dx = this.x - x; + double dy = this.y - y; + return dx * dx + dy * dy; + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + public float[] getHsv() { + return hsv; + } + + public float[] getHsvWithLightness(float lightness) { + if (hsvClone == null) + hsvClone = hsv.clone(); + hsvClone[0] = hsv[0]; + hsvClone[1] = hsv[1]; + hsvClone[2] = lightness; + return hsvClone; + } + + public void set(float x, float y, float[] hsv) { + this.x = x; + this.y = y; + this.hsv[0] = hsv[0]; + this.hsv[1] = hsv[1]; + this.hsv[2] = hsv[2]; + this.color = Color.HSVToColor(this.hsv); + } + + public int getColor() { + return color; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/ColorCircleDrawable.java b/app/src/main/java/com/colorpicker/ColorCircleDrawable.java new file mode 100644 index 0000000..97df2b7 --- /dev/null +++ b/app/src/main/java/com/colorpicker/ColorCircleDrawable.java @@ -0,0 +1,39 @@ +package com.colorpicker; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.ColorDrawable; + +import com.colorpicker.builder.PaintBuilder; + +public class ColorCircleDrawable extends ColorDrawable { + private float strokeWidth; + private Paint strokePaint = PaintBuilder.newPaint().style(Paint.Style.STROKE).stroke(strokeWidth).color(0xff9e9e9e).build(); + private Paint fillPaint = PaintBuilder.newPaint().style(Paint.Style.FILL).color(0).build(); + private Paint fillBackPaint = PaintBuilder.newPaint().shader(PaintBuilder.createAlphaPatternShader(26)).build(); + + public ColorCircleDrawable(int color) { + super(color); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawColor(0); + + int width = canvas.getWidth(); + float radius = width / 2f; + strokeWidth = radius / 8f; + + this.strokePaint.setStrokeWidth(strokeWidth); + this.fillPaint.setColor(getColor()); + canvas.drawCircle(radius, radius, radius - strokeWidth, fillBackPaint); + canvas.drawCircle(radius, radius, radius - strokeWidth, fillPaint); + canvas.drawCircle(radius, radius, radius - strokeWidth, strokePaint); + } + + @Override + public void setColor(int color) { + super.setColor(color); + invalidateSelf(); + } +} diff --git a/app/src/main/java/com/colorpicker/ColorPickerPreference.java b/app/src/main/java/com/colorpicker/ColorPickerPreference.java new file mode 100644 index 0000000..d1902e1 --- /dev/null +++ b/app/src/main/java/com/colorpicker/ColorPickerPreference.java @@ -0,0 +1,156 @@ +package com.colorpicker; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.preference.Preference; +import androidx.annotation.NonNull; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +import com.colorpicker.builder.ColorPickerClickListener; +import com.colorpicker.builder.ColorPickerDialogBuilder; +import com.muqingbfq.R; + +public class ColorPickerPreference extends Preference { + + protected boolean alphaSlider; + protected boolean lightSlider; + protected boolean border; + + protected int selectedColor = 0; + + protected ColorPickerView.WHEEL_TYPE wheelType; + protected int density; + + private boolean pickerColorEdit; + private String pickerTitle; + private String pickerButtonCancel; + private String pickerButtonOk; + + protected ImageView colorIndicator; + + public ColorPickerPreference(Context context) { + super(context); + } + + public ColorPickerPreference(Context context, AttributeSet attrs) { + super(context, attrs); + initWith(context, attrs); + } + + public ColorPickerPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initWith(context, attrs); + } + + private void initWith(Context context, AttributeSet attrs) { + final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColorPickerPreference); + + try { + alphaSlider = typedArray.getBoolean(R.styleable.ColorPickerPreference_alphaSlider, false); + lightSlider = typedArray.getBoolean(R.styleable.ColorPickerPreference_lightnessSlider, false); + border = typedArray.getBoolean(R.styleable.ColorPickerPreference_border, true); + + density = typedArray.getInt(R.styleable.ColorPickerPreference_density, 8); + wheelType = ColorPickerView.WHEEL_TYPE.indexOf(typedArray.getInt(R.styleable.ColorPickerPreference_wheelType, 0)); + + selectedColor = typedArray.getInt(R.styleable.ColorPickerPreference_initialColor, 0xffffffff); + + pickerColorEdit = typedArray.getBoolean(R.styleable.ColorPickerPreference_pickerColorEdit, true); + pickerTitle = typedArray.getString(R.styleable.ColorPickerPreference_pickerTitle); + if (pickerTitle==null) + pickerTitle = "Choose color"; + + pickerButtonCancel = typedArray.getString(R.styleable.ColorPickerPreference_pickerButtonCancel); + if (pickerButtonCancel==null) + pickerButtonCancel = "cancel"; + + pickerButtonOk = typedArray.getString(R.styleable.ColorPickerPreference_pickerButtonOk); + if (pickerButtonOk==null) + pickerButtonOk = "ok"; + + } finally { + typedArray.recycle(); + } + + setWidgetLayoutResource(R.layout.color_widget); + } + + + @Override + protected void onBindView(@NonNull View view) { + super.onBindView(view); + + int tmpColor = isEnabled() + ? selectedColor + : darken(selectedColor, .5f); + + colorIndicator = (ImageView) view.findViewById(R.id.color_indicator); + + ColorCircleDrawable colorChoiceDrawable = null; + Drawable currentDrawable = colorIndicator.getDrawable(); + if (currentDrawable != null && currentDrawable instanceof ColorCircleDrawable) + colorChoiceDrawable = (ColorCircleDrawable) currentDrawable; + + if (colorChoiceDrawable == null) + colorChoiceDrawable = new ColorCircleDrawable(tmpColor); + + colorIndicator.setImageDrawable(colorChoiceDrawable); + } + + public void setValue(int value) { + if (callChangeListener(value)) { + selectedColor = value; + persistInt(value); + notifyChanged(); + } + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + setValue(restoreValue ? getPersistedInt(0) : (Integer) defaultValue); + } + + @Override + protected void onClick() { + ColorPickerDialogBuilder builder = ColorPickerDialogBuilder + .with(getContext()) + .setTitle(pickerTitle) + .initialColor(selectedColor) + .showBorder(border) + .wheelType(wheelType) + .density(density) + .showColorEdit(pickerColorEdit) + .setPositiveButton(pickerButtonOk, new ColorPickerClickListener() { + @Override + public void onClick(DialogInterface dialog, int selectedColorFromPicker, Integer[] allColors) { + setValue(selectedColorFromPicker); + } + }) + .setNegativeButton(pickerButtonCancel, null); + + if (!alphaSlider && !lightSlider) builder.noSliders(); + else if (!alphaSlider) builder.lightnessSliderOnly(); + else if (!lightSlider) builder.alphaSliderOnly(); + + builder + .build() + .show(); + } + + public static int darken(int color, float factor) { + int a = Color.alpha(color); + int r = Color.red(color); + int g = Color.green(color); + int b = Color.blue(color); + + return Color.argb(a, + Math.max((int)(r * factor), 0), + Math.max((int)(g * factor), 0), + Math.max((int)(b * factor), 0)); + } +} diff --git a/app/src/main/java/com/colorpicker/ColorPickerView.java b/app/src/main/java/com/colorpicker/ColorPickerView.java new file mode 100644 index 0000000..7a9521f --- /dev/null +++ b/app/src/main/java/com/colorpicker/ColorPickerView.java @@ -0,0 +1,572 @@ +package com.colorpicker; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.colorpicker.builder.ColorWheelRendererBuilder; +import com.colorpicker.builder.PaintBuilder; +import com.colorpicker.renderer.ColorWheelRenderOption; +import com.colorpicker.renderer.ColorWheelRenderer; +import com.colorpicker.slider.AlphaSlider; +import com.colorpicker.slider.LightnessSlider; +import com.muqingbfq.R; + +import java.util.ArrayList; +public class ColorPickerView extends View { + private static final float STROKE_RATIO = 1.5f; + + private Bitmap colorWheel; + private Canvas colorWheelCanvas; + private Bitmap currentColor; + private Canvas currentColorCanvas; + private boolean showBorder; + private int density = 8; + + private float lightness = 1; + private float alpha = 1; + private int backgroundColor = 0x00000000; + + private Integer initialColors[] = new Integer[]{null, null, null, null, null}; + private int colorSelection = 0; + private Integer initialColor; + private Integer pickerColorEditTextColor; + private Paint colorWheelFill = PaintBuilder.newPaint().color(0).build(); + private Paint selectorStroke = PaintBuilder.newPaint().color(0).build(); + private Paint alphaPatternPaint = PaintBuilder.newPaint().build(); + private ColorCircle currentColorCircle; + + private ArrayList colorChangedListeners = new ArrayList(); + private ArrayList listeners = new ArrayList(); + + private LightnessSlider lightnessSlider; + private AlphaSlider alphaSlider; + private EditText colorEdit; + private TextWatcher colorTextChange = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + try { + int color = Color.parseColor(s.toString()); + + // set the color without changing the edit text preventing stack overflow + setColor(color, false); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void afterTextChanged(Editable s) { + } + }; + private LinearLayout colorPreview; + + private ColorWheelRenderer renderer; + + private int alphaSliderViewId, lightnessSliderViewId; + + public ColorPickerView(Context context) { + super(context); + initWith(context, null); + } + + public ColorPickerView(Context context, AttributeSet attrs) { + super(context, attrs); + initWith(context, attrs); + } + + public ColorPickerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initWith(context, attrs); + } + + @TargetApi(21) + public ColorPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initWith(context, attrs); + } + + private void initWith(Context context, AttributeSet attrs) { + final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColorPickerPreference); + + density = typedArray.getInt(R.styleable.ColorPickerPreference_density, 10); + initialColor = typedArray.getInt(R.styleable.ColorPickerPreference_initialColor, 0xffffffff); + + pickerColorEditTextColor = typedArray.getInt(R.styleable.ColorPickerPreference_pickerColorEditTextColor, 0xffffffff); + + WHEEL_TYPE wheelType = WHEEL_TYPE.indexOf(typedArray.getInt(R.styleable.ColorPickerPreference_wheelType, 0)); + ColorWheelRenderer renderer = ColorWheelRendererBuilder.getRenderer(wheelType); + + alphaSliderViewId = typedArray.getResourceId(R.styleable.ColorPickerPreference_alphaSliderView, 0); + lightnessSliderViewId = typedArray.getResourceId(R.styleable.ColorPickerPreference_lightnessSliderView, 0); + + setRenderer(renderer); + setDensity(density); + setInitialColor(initialColor, true); + + typedArray.recycle(); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + updateColorWheel(); + currentColorCircle = findNearestByColor(initialColor); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (alphaSliderViewId != 0) + setAlphaSlider((AlphaSlider) getRootView().findViewById(alphaSliderViewId)); + if (lightnessSliderViewId != 0) + setLightnessSlider((LightnessSlider) getRootView().findViewById(lightnessSliderViewId)); + + updateColorWheel(); + currentColorCircle = findNearestByColor(initialColor); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + updateColorWheel(); + } + + private void updateColorWheel() { + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + + if (height < width) + width = height; + if (width <= 0) + return; + if (colorWheel == null || colorWheel.getWidth() != width) { + colorWheel = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888); + colorWheelCanvas = new Canvas(colorWheel); + alphaPatternPaint.setShader(PaintBuilder.createAlphaPatternShader(26)); + } + if (currentColor == null || currentColor.getWidth() != width) { + currentColor = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888); + currentColorCanvas = new Canvas(currentColor); + } + drawColorWheel(); + invalidate(); + } + + private void drawColorWheel() { + colorWheelCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + currentColorCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + + if (renderer == null) return; + + float half = colorWheelCanvas.getWidth() / 2f; + float strokeWidth = STROKE_RATIO * (1f + ColorWheelRenderer.GAP_PERCENTAGE); + float maxRadius = half - strokeWidth - half / density; + float cSize = maxRadius / (density - 1) / 2; + + ColorWheelRenderOption colorWheelRenderOption = renderer.getRenderOption(); + colorWheelRenderOption.density = this.density; + colorWheelRenderOption.maxRadius = maxRadius; + colorWheelRenderOption.cSize = cSize; + colorWheelRenderOption.strokeWidth = strokeWidth; + colorWheelRenderOption.alpha = alpha; + colorWheelRenderOption.lightness = lightness; + colorWheelRenderOption.targetCanvas = colorWheelCanvas; + + renderer.initWith(colorWheelRenderOption); + renderer.draw(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int width = 0; + if (widthMode == MeasureSpec.UNSPECIFIED) + width = widthMeasureSpec; + else if (widthMode == MeasureSpec.AT_MOST) + width = MeasureSpec.getSize(widthMeasureSpec); + else if (widthMode == MeasureSpec.EXACTLY) + width = MeasureSpec.getSize(widthMeasureSpec); + + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int height = 0; + if (heightMode == MeasureSpec.UNSPECIFIED) + height = heightMeasureSpec; + else if (heightMode == MeasureSpec.AT_MOST) + height = MeasureSpec.getSize(heightMeasureSpec); + else if (heightMode == MeasureSpec.EXACTLY) + height = MeasureSpec.getSize(heightMeasureSpec); + int squareDimen = width; + if (height < width) + squareDimen = height; + setMeasuredDimension(squareDimen, squareDimen); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: { + int lastSelectedColor = getSelectedColor(); + currentColorCircle = findNearestByPosition(event.getX(), event.getY()); + int selectedColor = getSelectedColor(); + + callOnColorChangedListeners(lastSelectedColor, selectedColor); + + initialColor = selectedColor; + setColorToSliders(selectedColor); + updateColorWheel(); + invalidate(); + break; + } + case MotionEvent.ACTION_UP: { + int selectedColor = getSelectedColor(); + if (listeners != null) { + for (OnColorSelectedListener listener : listeners) { + try { + listener.onColorSelected(selectedColor); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + setColorToSliders(selectedColor); + setColorText(selectedColor); + setColorPreviewColor(selectedColor); + invalidate(); + break; + } + } + return true; + } + + protected void callOnColorChangedListeners(int oldColor, int newColor) { + if (colorChangedListeners != null && oldColor != newColor) { + for (OnColorChangedListener listener : colorChangedListeners) { + try { + listener.onColorChanged(newColor); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawColor(backgroundColor); + + float maxRadius = canvas.getWidth() / (1f + ColorWheelRenderer.GAP_PERCENTAGE); + float size = maxRadius / density / 2; + if (colorWheel != null && currentColorCircle != null) { + colorWheelFill.setColor(Color.HSVToColor(currentColorCircle.getHsvWithLightness(this.lightness))); + colorWheelFill.setAlpha((int) (alpha * 0xff)); + + // a separate canvas is used to erase an issue with the alpha pattern around the edges + // draw circle slightly larger than it needs to be, then erase edges to proper dimensions + currentColorCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + 4, alphaPatternPaint); + currentColorCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + 4, colorWheelFill); + + selectorStroke = PaintBuilder.newPaint().color(0xffffffff).style(Paint.Style.STROKE).stroke(size * (STROKE_RATIO - 1)).xPerMode(PorterDuff.Mode.CLEAR).build(); + + if (showBorder) colorWheelCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + (selectorStroke.getStrokeWidth() / 2f), selectorStroke); + canvas.drawBitmap(colorWheel, 0, 0, null); + + currentColorCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + (selectorStroke.getStrokeWidth() / 2f), selectorStroke); + canvas.drawBitmap(currentColor, 0, 0, null); + } + } + + private ColorCircle findNearestByPosition(float x, float y) { + ColorCircle near = null; + double minDist = Double.MAX_VALUE; + + for (ColorCircle colorCircle : renderer.getColorCircleList()) { + double dist = colorCircle.sqDist(x, y); + if (minDist > dist) { + minDist = dist; + near = colorCircle; + } + } + + return near; + } + + private ColorCircle findNearestByColor(int color) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + ColorCircle near = null; + double minDiff = Double.MAX_VALUE; + double x = hsv[1] * Math.cos(hsv[0] * Math.PI / 180); + double y = hsv[1] * Math.sin(hsv[0] * Math.PI / 180); + + for (ColorCircle colorCircle : renderer.getColorCircleList()) { + float[] hsv1 = colorCircle.getHsv(); + double x1 = hsv1[1] * Math.cos(hsv1[0] * Math.PI / 180); + double y1 = hsv1[1] * Math.sin(hsv1[0] * Math.PI / 180); + double dx = x - x1; + double dy = y - y1; + double dist = dx * dx + dy * dy; + if (dist < minDiff) { + minDiff = dist; + near = colorCircle; + } + } + + return near; + } + + public int getSelectedColor() { + int color = 0; + if (currentColorCircle != null) + color = Utils.colorAtLightness(currentColorCircle.getColor(), this.lightness); + return Utils.adjustAlpha(this.alpha, color); + } + + public Integer[] getAllColors() { + return initialColors; + } + + public void setInitialColors(Integer[] colors, int selectedColor) { + this.initialColors = colors; + this.colorSelection = selectedColor; + Integer initialColor = this.initialColors[this.colorSelection]; + if (initialColor == null) initialColor = 0xffffffff; + setInitialColor(initialColor, true); + } + + public void setInitialColor(int color, boolean updateText) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + + this.alpha = Utils.getAlphaPercent(color); + this.lightness = hsv[2]; + this.initialColors[this.colorSelection] = color; + this.initialColor = color; + setColorPreviewColor(color); + setColorToSliders(color); + if (this.colorEdit != null && updateText) + setColorText(color); + currentColorCircle = findNearestByColor(color); + } + + public void setLightness(float lightness) { + int lastSelectedColor = getSelectedColor(); + + this.lightness = lightness; + if (currentColorCircle != null) { + this.initialColor = Color.HSVToColor(Utils.alphaValueAsInt(this.alpha), currentColorCircle.getHsvWithLightness(lightness)); + if (this.colorEdit != null) + this.colorEdit.setText(Utils.getHexString(this.initialColor, this.alphaSlider != null)); + if (this.alphaSlider != null && this.initialColor != null) + this.alphaSlider.setColor(this.initialColor); + + callOnColorChangedListeners(lastSelectedColor, this.initialColor); + + updateColorWheel(); + invalidate(); + } + } + + public void setColor(int color, boolean updateText) { + setInitialColor(color, updateText); + updateColorWheel(); + invalidate(); + } + + public void setAlphaValue(float alpha) { + int lastSelectedColor = getSelectedColor(); + + this.alpha = alpha; + this.initialColor = Color.HSVToColor(Utils.alphaValueAsInt(this.alpha), currentColorCircle.getHsvWithLightness(this.lightness)); + if (this.colorEdit != null) + this.colorEdit.setText(Utils.getHexString(this.initialColor, this.alphaSlider != null)); + if (this.lightnessSlider != null && this.initialColor != null) + this.lightnessSlider.setColor(this.initialColor); + + callOnColorChangedListeners(lastSelectedColor, this.initialColor); + + updateColorWheel(); + invalidate(); + } + + public void addOnColorChangedListener(OnColorChangedListener listener) { + this.colorChangedListeners.add(listener); + } + + public void addOnColorSelectedListener(OnColorSelectedListener listener) { + this.listeners.add(listener); + } + + public void setLightnessSlider(LightnessSlider lightnessSlider) { + this.lightnessSlider = lightnessSlider; + if (lightnessSlider != null) { + this.lightnessSlider.setColorPicker(this); + this.lightnessSlider.setColor(getSelectedColor()); + } + } + + public void setAlphaSlider(AlphaSlider alphaSlider) { + this.alphaSlider = alphaSlider; + if (alphaSlider != null) { + this.alphaSlider.setColorPicker(this); + this.alphaSlider.setColor(getSelectedColor()); + } + } + + public void setColorEdit(EditText colorEdit) { + this.colorEdit = colorEdit; + if (this.colorEdit != null) { + this.colorEdit.setVisibility(View.VISIBLE); + this.colorEdit.addTextChangedListener(colorTextChange); + setColorEditTextColor(pickerColorEditTextColor); + } + } + + public void setColorEditTextColor(int argb) { + this.pickerColorEditTextColor = argb; + if (colorEdit != null) + colorEdit.setTextColor(argb); + } + + public void setDensity(int density) { + this.density = Math.max(2, density); + invalidate(); + } + + public void setRenderer(ColorWheelRenderer renderer) { + this.renderer = renderer; + invalidate(); + } + + public void setColorPreview(LinearLayout colorPreview, Integer selectedColor) { + if (colorPreview == null) + return; + this.colorPreview = colorPreview; + if (selectedColor == null) + selectedColor = 0; + int children = colorPreview.getChildCount(); + if (children == 0 || colorPreview.getVisibility() != View.VISIBLE) + return; + + for (int i = 0; i < children; i++) { + View childView = colorPreview.getChildAt(i); + if (!(childView instanceof LinearLayout)) + continue; + LinearLayout childLayout = (LinearLayout) childView; + if (i == selectedColor) { + childLayout.setBackgroundColor(Color.WHITE); + } + ImageView childImage = (ImageView) childLayout.findViewById(R.id.image_preview); + childImage.setClickable(true); + childImage.setTag(i); + childImage.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (v == null) + return; + Object tag = v.getTag(); + if (tag == null || !(tag instanceof Integer)) + return; + setSelectedColor((int) tag); + } + }); + } + } + + public void setSelectedColor(int previewNumber) { + if (initialColors == null || initialColors.length < previewNumber) + return; + this.colorSelection = previewNumber; + setHighlightedColor(previewNumber); + Integer color = initialColors[previewNumber]; + if (color == null) + return; + setColor(color, true); + } + + public void setShowBorder(boolean showBorder) { + this.showBorder = showBorder; + } + + private void setHighlightedColor(int previewNumber) { + int children = colorPreview.getChildCount(); + if (children == 0 || colorPreview.getVisibility() != View.VISIBLE) + return; + + for (int i = 0; i < children; i++) { + View childView = colorPreview.getChildAt(i); + if (!(childView instanceof LinearLayout)) + continue; + LinearLayout childLayout = (LinearLayout) childView; + if (i == previewNumber) { + childLayout.setBackgroundColor(Color.WHITE); + } else { + childLayout.setBackgroundColor(Color.TRANSPARENT); + } + } + } + + private void setColorPreviewColor(int newColor) { + if (colorPreview == null || initialColors == null || colorSelection > initialColors.length || initialColors[colorSelection] == null) + return; + + int children = colorPreview.getChildCount(); + if (children == 0 || colorPreview.getVisibility() != View.VISIBLE) + return; + + View childView = colorPreview.getChildAt(colorSelection); + if (!(childView instanceof LinearLayout)) + return; + LinearLayout childLayout = (LinearLayout) childView; + ImageView childImage = (ImageView) childLayout.findViewById(R.id.image_preview); + childImage.setImageDrawable(new ColorCircleDrawable(newColor)); + } + + private void setColorText(int argb) { + if (colorEdit == null) + return; + colorEdit.setText(Utils.getHexString(argb, this.alphaSlider != null)); + } + + private void setColorToSliders(int selectedColor) { + if (lightnessSlider != null) + lightnessSlider.setColor(selectedColor); + if (alphaSlider != null) + alphaSlider.setColor(selectedColor); + } + + public enum WHEEL_TYPE { + FLOWER, CIRCLE; + + public static WHEEL_TYPE indexOf(int index) { + switch (index) { + case 0: + return FLOWER; + case 1: + return CIRCLE; + } + return FLOWER; + } + } +} diff --git a/app/src/main/java/com/colorpicker/OnColorChangedListener.java b/app/src/main/java/com/colorpicker/OnColorChangedListener.java new file mode 100644 index 0000000..ab2d209 --- /dev/null +++ b/app/src/main/java/com/colorpicker/OnColorChangedListener.java @@ -0,0 +1,5 @@ +package com.colorpicker; + +public interface OnColorChangedListener { + void onColorChanged(int selectedColor); +} diff --git a/app/src/main/java/com/colorpicker/OnColorSelectedListener.java b/app/src/main/java/com/colorpicker/OnColorSelectedListener.java new file mode 100644 index 0000000..a4213f9 --- /dev/null +++ b/app/src/main/java/com/colorpicker/OnColorSelectedListener.java @@ -0,0 +1,5 @@ +package com.colorpicker; + +public interface OnColorSelectedListener { + void onColorSelected(int selectedColor); +} diff --git a/app/src/main/java/com/colorpicker/Utils.java b/app/src/main/java/com/colorpicker/Utils.java new file mode 100644 index 0000000..94ba331 --- /dev/null +++ b/app/src/main/java/com/colorpicker/Utils.java @@ -0,0 +1,40 @@ +package com.colorpicker; + +import android.graphics.Color; + +/** + * Created by Charles Andersons on 4/17/15. + */ +public class Utils { + public static float getAlphaPercent(int argb) { + return Color.alpha(argb) / 255f; + } + + public static int alphaValueAsInt(float alpha) { + return Math.round(alpha * 255); + } + + public static int adjustAlpha(float alpha, int color) { + return alphaValueAsInt(alpha) << 24 | (0x00ffffff & color); + } + + public static int colorAtLightness(int color, float lightness) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + hsv[2] = lightness; + return Color.HSVToColor(hsv); + } + + public static float lightnessOfColor(int color) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + return hsv[2]; + } + + public static String getHexString(int color, boolean showAlpha) { + int base = showAlpha ? 0xFFFFFFFF : 0xFFFFFF; + String format = showAlpha ? "#%08X" : "#%06X"; + return String.format(format, (base & color)).toUpperCase(); + } + +} diff --git a/app/src/main/java/com/colorpicker/builder/ColorPickerClickListener.java b/app/src/main/java/com/colorpicker/builder/ColorPickerClickListener.java new file mode 100644 index 0000000..f1e123a --- /dev/null +++ b/app/src/main/java/com/colorpicker/builder/ColorPickerClickListener.java @@ -0,0 +1,10 @@ +package com.colorpicker.builder; + +import android.content.DialogInterface; + +/** + * Created by Charles Anderson on 4/17/15. + */ +public interface ColorPickerClickListener { + void onClick(DialogInterface d, int lastSelectedColor, Integer[] allColors); +} diff --git a/app/src/main/java/com/colorpicker/builder/ColorPickerDialogBuilder.java b/app/src/main/java/com/colorpicker/builder/ColorPickerDialogBuilder.java new file mode 100644 index 0000000..c7dbfde --- /dev/null +++ b/app/src/main/java/com/colorpicker/builder/ColorPickerDialogBuilder.java @@ -0,0 +1,297 @@ +package com.colorpicker.builder; + +import androidx.appcompat.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.text.InputFilter; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.colorpicker.ColorPickerView; +import com.colorpicker.OnColorChangedListener; +import com.colorpicker.OnColorSelectedListener; +import com.colorpicker.Utils; +import com.colorpicker.renderer.ColorWheelRenderer; +import com.colorpicker.slider.AlphaSlider; +import com.colorpicker.slider.LightnessSlider; +import com.muqingbfq.R; + +public class ColorPickerDialogBuilder { + private MaterialAlertDialogBuilder builder; + private LinearLayout pickerContainer; + private ColorPickerView colorPickerView; + private LightnessSlider lightnessSlider; + private AlphaSlider alphaSlider; + private EditText colorEdit; + private LinearLayout colorPreview; + + private boolean isLightnessSliderEnabled = true; + private boolean isAlphaSliderEnabled = true; + private boolean isBorderEnabled = true; + private boolean isColorEditEnabled = false; + private boolean isPreviewEnabled = false; + private int pickerCount = 1; + private int defaultMargin = 0; + private int defaultMarginTop = 0; + private Integer[] initialColor = new Integer[]{null, null, null, null, null}; + + private ColorPickerDialogBuilder(Context context) { + this(context, 0); + } + + private ColorPickerDialogBuilder(Context context, int theme) { + defaultMargin = getDimensionAsPx(context, R.dimen.default_slider_margin); + defaultMarginTop = getDimensionAsPx(context, R.dimen.default_margin_top); + + builder = new MaterialAlertDialogBuilder(context, theme); + pickerContainer = new LinearLayout(context); + pickerContainer.setOrientation(LinearLayout.VERTICAL); + pickerContainer.setGravity(Gravity.CENTER_HORIZONTAL); + pickerContainer.setPadding(defaultMargin, defaultMarginTop, defaultMargin, 0); + + LinearLayout.LayoutParams layoutParamsForColorPickerView = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); + layoutParamsForColorPickerView.weight = 1; + colorPickerView = new ColorPickerView(context); + + pickerContainer.addView(colorPickerView, layoutParamsForColorPickerView); + + builder.setView(pickerContainer); + } + + public static ColorPickerDialogBuilder with(Context context) { + return new ColorPickerDialogBuilder(context); + } + + public static ColorPickerDialogBuilder with(Context context, int theme) { + return new ColorPickerDialogBuilder(context, theme); + } + + public ColorPickerDialogBuilder setTitle(String title) { + builder.setTitle(title); + return this; + } + + public ColorPickerDialogBuilder setTitle(int titleId) { + builder.setTitle(titleId); + return this; + } + + public ColorPickerDialogBuilder initialColor(int initialColor) { + this.initialColor[0] = initialColor; + return this; + } + + public ColorPickerDialogBuilder initialColors(int[] initialColor) { + for (int i = 0; i < initialColor.length && i < this.initialColor.length; i++) { + this.initialColor[i] = initialColor[i]; + } + return this; + } + + public ColorPickerDialogBuilder wheelType(ColorPickerView.WHEEL_TYPE wheelType) { + ColorWheelRenderer renderer = ColorWheelRendererBuilder.getRenderer(wheelType); + colorPickerView.setRenderer(renderer); + return this; + } + + public ColorPickerDialogBuilder density(int density) { + colorPickerView.setDensity(density); + return this; + } + + public ColorPickerDialogBuilder setOnColorChangedListener(OnColorChangedListener onColorChangedListener) { + colorPickerView.addOnColorChangedListener(onColorChangedListener); + return this; + } + + public ColorPickerDialogBuilder setOnColorSelectedListener(OnColorSelectedListener onColorSelectedListener) { + colorPickerView.addOnColorSelectedListener(onColorSelectedListener); + return this; + } + + public ColorPickerDialogBuilder setPositiveButton(CharSequence text, final ColorPickerClickListener onClickListener) { + builder.setPositiveButton(text, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + positiveButtonOnClick(dialog, onClickListener); + } + }); + return this; + } + + public ColorPickerDialogBuilder setPositiveButton(int textId, final ColorPickerClickListener onClickListener) { + builder.setPositiveButton(textId, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + positiveButtonOnClick(dialog, onClickListener); + } + }); + return this; + } + + public ColorPickerDialogBuilder setNegativeButton(CharSequence text, DialogInterface.OnClickListener onClickListener) { + builder.setNegativeButton(text, onClickListener); + return this; + } + + public ColorPickerDialogBuilder setNegativeButton(int textId, DialogInterface.OnClickListener onClickListener) { + builder.setNegativeButton(textId, onClickListener); + return this; + } + + public ColorPickerDialogBuilder noSliders() { + isLightnessSliderEnabled = false; + isAlphaSliderEnabled = false; + return this; + } + + public ColorPickerDialogBuilder alphaSliderOnly() { + isLightnessSliderEnabled = false; + isAlphaSliderEnabled = true; + return this; + } + + public ColorPickerDialogBuilder lightnessSliderOnly() { + isLightnessSliderEnabled = true; + isAlphaSliderEnabled = false; + return this; + } + + public ColorPickerDialogBuilder showAlphaSlider(boolean showAlpha) { + isAlphaSliderEnabled = showAlpha; + return this; + } + + public ColorPickerDialogBuilder showLightnessSlider(boolean showLightness) { + isLightnessSliderEnabled = showLightness; + return this; + } + + public ColorPickerDialogBuilder showBorder(boolean showBorder) { + isBorderEnabled = showBorder; + return this; + } + + public ColorPickerDialogBuilder showColorEdit(boolean showEdit) { + isColorEditEnabled = showEdit; + return this; + } + + public ColorPickerDialogBuilder setColorEditTextColor(int argb) { + colorPickerView.setColorEditTextColor(argb); + return this; + } + + public ColorPickerDialogBuilder showColorPreview(boolean showPreview) { + isPreviewEnabled = showPreview; + if (!showPreview) + pickerCount = 1; + return this; + } + + public ColorPickerDialogBuilder setPickerCount(int pickerCount) throws IndexOutOfBoundsException { + if (pickerCount < 1 || pickerCount > 5) + throw new IndexOutOfBoundsException("Picker Can Only Support 1-5 Colors"); + this.pickerCount = pickerCount; + if (this.pickerCount > 1) + this.isPreviewEnabled = true; + return this; + } + + public AlertDialog build() { + Context context = builder.getContext(); + colorPickerView.setInitialColors(initialColor, getStartOffset(initialColor)); + colorPickerView.setShowBorder(isBorderEnabled); + + if (isLightnessSliderEnabled) { + LinearLayout.LayoutParams layoutParamsForLightnessBar = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getDimensionAsPx(context, R.dimen.default_slider_height)); + lightnessSlider = new LightnessSlider(context); + lightnessSlider.setLayoutParams(layoutParamsForLightnessBar); + pickerContainer.addView(lightnessSlider); + colorPickerView.setLightnessSlider(lightnessSlider); + lightnessSlider.setColor(getStartColor(initialColor)); + lightnessSlider.setShowBorder(isBorderEnabled); + } + if (isAlphaSliderEnabled) { + LinearLayout.LayoutParams layoutParamsForAlphaBar = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getDimensionAsPx(context, R.dimen.default_slider_height)); + alphaSlider = new AlphaSlider(context); + alphaSlider.setLayoutParams(layoutParamsForAlphaBar); + pickerContainer.addView(alphaSlider); + colorPickerView.setAlphaSlider(alphaSlider); + alphaSlider.setColor(getStartColor(initialColor)); + alphaSlider.setShowBorder(isBorderEnabled); + } + if (isColorEditEnabled) { + LinearLayout.LayoutParams layoutParamsForColorEdit = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + colorEdit = (EditText) View.inflate(context, R.layout.color_edit, null); + colorEdit.setFilters(new InputFilter[]{new InputFilter.AllCaps()}); + colorEdit.setSingleLine(); + colorEdit.setVisibility(View.GONE); + + // limit number of characters to hexColors + int maxLength = isAlphaSliderEnabled ? 9 : 7; + colorEdit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)}); + + pickerContainer.addView(colorEdit, layoutParamsForColorEdit); + + colorEdit.setText(Utils.getHexString(getStartColor(initialColor), isAlphaSliderEnabled)); + colorPickerView.setColorEdit(colorEdit); + } + if (isPreviewEnabled) { + colorPreview = (LinearLayout) View.inflate(context, R.layout.color_preview, null); + colorPreview.setVisibility(View.GONE); + pickerContainer.addView(colorPreview); + + if (initialColor.length == 0) { + ImageView colorImage = (ImageView) View.inflate(context, R.layout.color_selector, null); + colorImage.setImageDrawable(new ColorDrawable(Color.WHITE)); + } else { + for (int i = 0; i < initialColor.length && i < this.pickerCount; i++) { + if (initialColor[i] == null) + break; + LinearLayout colorLayout = (LinearLayout) View.inflate(context, R.layout.color_selector, null); + ImageView colorImage = (ImageView) colorLayout.findViewById(R.id.image_preview); + colorImage.setImageDrawable(new ColorDrawable(initialColor[i])); + colorPreview.addView(colorLayout); + } + } + colorPreview.setVisibility(View.VISIBLE); + colorPickerView.setColorPreview(colorPreview, getStartOffset(initialColor)); + } + + return builder.create(); + } + + private Integer getStartOffset(Integer[] colors) { + Integer start = 0; + for (int i = 0; i < colors.length; i++) { + if (colors[i] == null) { + return start; + } + start = (i + 1) / 2; + } + return start; + } + + private int getStartColor(Integer[] colors) { + Integer startColor = getStartOffset(colors); + return startColor == null ? Color.WHITE : colors[startColor]; + } + + private static int getDimensionAsPx(Context context, int rid) { + return (int) (context.getResources().getDimension(rid) + .5f); + } + + private void positiveButtonOnClick(DialogInterface dialog, ColorPickerClickListener onClickListener) { + int selectedColor = colorPickerView.getSelectedColor(); + Integer[] allColors = colorPickerView.getAllColors(); + onClickListener.onClick(dialog, selectedColor, allColors); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/builder/ColorWheelRendererBuilder.java b/app/src/main/java/com/colorpicker/builder/ColorWheelRendererBuilder.java new file mode 100644 index 0000000..dcde8ab --- /dev/null +++ b/app/src/main/java/com/colorpicker/builder/ColorWheelRendererBuilder.java @@ -0,0 +1,18 @@ +package com.colorpicker.builder; + +import com.colorpicker.ColorPickerView; +import com.colorpicker.renderer.ColorWheelRenderer; +import com.colorpicker.renderer.FlowerColorWheelRenderer; +import com.colorpicker.renderer.SimpleColorWheelRenderer; + +public class ColorWheelRendererBuilder { + public static ColorWheelRenderer getRenderer(ColorPickerView.WHEEL_TYPE wheelType) { + switch (wheelType) { + case CIRCLE: + return new SimpleColorWheelRenderer(); + case FLOWER: + return new FlowerColorWheelRenderer(); + } + throw new IllegalArgumentException("wrong WHEEL_TYPE"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/builder/PaintBuilder.java b/app/src/main/java/com/colorpicker/builder/PaintBuilder.java new file mode 100644 index 0000000..31d2d4d --- /dev/null +++ b/app/src/main/java/com/colorpicker/builder/PaintBuilder.java @@ -0,0 +1,82 @@ +package com.colorpicker.builder; + +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Shader; + +public class PaintBuilder { + public static PaintHolder newPaint() { + return new PaintHolder(); + } + + public static class PaintHolder { + private Paint paint; + + private PaintHolder() { + this.paint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + + public PaintHolder color(int color) { + this.paint.setColor(color); + return this; + } + + public PaintHolder antiAlias(boolean flag) { + this.paint.setAntiAlias(flag); + return this; + } + + public PaintHolder style(Paint.Style style) { + this.paint.setStyle(style); + return this; + } + + public PaintHolder mode(PorterDuff.Mode mode) { + this.paint.setXfermode(new PorterDuffXfermode(mode)); + return this; + } + + public PaintHolder stroke(float width) { + this.paint.setStrokeWidth(width); + return this; + } + + public PaintHolder xPerMode(PorterDuff.Mode mode) { + this.paint.setXfermode(new PorterDuffXfermode(mode)); + return this; + } + + public PaintHolder shader(Shader shader) { + this.paint.setShader(shader); + return this; + } + + public Paint build() { + return this.paint; + } + } + + public static Shader createAlphaPatternShader(int size) { + size /= 2; + size = Math.max(8, size * 2); + return new BitmapShader(createAlphaBackgroundPattern(size), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); + } + + private static Bitmap createAlphaBackgroundPattern(int size) { + Paint alphaPatternPaint = PaintBuilder.newPaint().build(); + Bitmap bm = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bm); + int s = Math.round(size / 2f); + for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) { + if ((i + j) % 2 == 0) alphaPatternPaint.setColor(0xffffffff); + else alphaPatternPaint.setColor(0xffd0d0d0); + c.drawRect(i * s, j * s, (i + 1) * s, (j + 1) * s, alphaPatternPaint); + } + return bm; + } +} diff --git a/app/src/main/java/com/colorpicker/renderer/AbsColorWheelRenderer.java b/app/src/main/java/com/colorpicker/renderer/AbsColorWheelRenderer.java new file mode 100644 index 0000000..c6cfe9f --- /dev/null +++ b/app/src/main/java/com/colorpicker/renderer/AbsColorWheelRenderer.java @@ -0,0 +1,34 @@ +package com.colorpicker.renderer; + +import com.colorpicker.ColorCircle; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AbsColorWheelRenderer implements ColorWheelRenderer { + protected ColorWheelRenderOption colorWheelRenderOption; + protected List colorCircleList = new ArrayList<>(); + + public void initWith(ColorWheelRenderOption colorWheelRenderOption) { + this.colorWheelRenderOption = colorWheelRenderOption; + this.colorCircleList.clear(); + } + + @Override + public ColorWheelRenderOption getRenderOption() { + if (colorWheelRenderOption == null) colorWheelRenderOption = new ColorWheelRenderOption(); + return colorWheelRenderOption; + } + + public List getColorCircleList() { + return colorCircleList; + } + + protected int getAlphaValueAsInt() { + return Math.round(colorWheelRenderOption.alpha * 255); + } + + protected int calcTotalCount(float radius, float size) { + return Math.max(1, (int) ((1f - GAP_PERCENTAGE) * Math.PI / (Math.asin(size / radius)) + 0.5f)); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/renderer/ColorWheelRenderOption.java b/app/src/main/java/com/colorpicker/renderer/ColorWheelRenderOption.java new file mode 100644 index 0000000..0eda4cd --- /dev/null +++ b/app/src/main/java/com/colorpicker/renderer/ColorWheelRenderOption.java @@ -0,0 +1,10 @@ +package com.colorpicker.renderer; + +import android.graphics.Canvas; + +public class ColorWheelRenderOption { + public int density; + public float maxRadius; + public float cSize, strokeWidth, alpha, lightness; + public Canvas targetCanvas; +} \ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/renderer/ColorWheelRenderer.java b/app/src/main/java/com/colorpicker/renderer/ColorWheelRenderer.java new file mode 100644 index 0000000..0caab95 --- /dev/null +++ b/app/src/main/java/com/colorpicker/renderer/ColorWheelRenderer.java @@ -0,0 +1,16 @@ +package com.colorpicker.renderer; + +import com.colorpicker.ColorCircle; +import java.util.List; + +public interface ColorWheelRenderer { + float GAP_PERCENTAGE = 0.025f; + + void draw(); + + ColorWheelRenderOption getRenderOption(); + + void initWith(ColorWheelRenderOption colorWheelRenderOption); + + List getColorCircleList(); +} diff --git a/app/src/main/java/com/colorpicker/renderer/FlowerColorWheelRenderer.java b/app/src/main/java/com/colorpicker/renderer/FlowerColorWheelRenderer.java new file mode 100644 index 0000000..3eb60c8 --- /dev/null +++ b/app/src/main/java/com/colorpicker/renderer/FlowerColorWheelRenderer.java @@ -0,0 +1,50 @@ +package com.colorpicker.renderer; + +import android.graphics.Color; +import android.graphics.Paint; + +import com.colorpicker.ColorCircle; +import com.colorpicker.builder.PaintBuilder; + +public class FlowerColorWheelRenderer extends AbsColorWheelRenderer { + private Paint selectorFill = PaintBuilder.newPaint().build(); + private float[] hsv = new float[3]; + private float sizeJitter = 1.2f; + + @Override + public void draw() { + final int setSize = colorCircleList.size(); + int currentCount = 0; + float half = colorWheelRenderOption.targetCanvas.getWidth() / 2f; + int density = colorWheelRenderOption.density; + float strokeWidth = colorWheelRenderOption.strokeWidth; + float maxRadius = colorWheelRenderOption.maxRadius; + float cSize = colorWheelRenderOption.cSize; + + for (int i = 0; i < density; i++) { + float p = (float) i / (density - 1); // 0~1 + float jitter = (i - density / 2f) / density; // -0.5 ~ 0.5 + float radius = maxRadius * p; + float size = Math.max(1.5f + strokeWidth, cSize + (i == 0 ? 0 : cSize * sizeJitter * jitter)); + int total = Math.min(calcTotalCount(radius, size), density * 2); + + for (int j = 0; j < total; j++) { + double angle = Math.PI * 2 * j / total + (Math.PI / total) * ((i + 1) % 2); + float x = half + (float) (radius * Math.cos(angle)); + float y = half + (float) (radius * Math.sin(angle)); + hsv[0] = (float) (angle * 180 / Math.PI); + hsv[1] = radius / maxRadius; + hsv[2] = colorWheelRenderOption.lightness; + selectorFill.setColor(Color.HSVToColor(hsv)); + selectorFill.setAlpha(getAlphaValueAsInt()); + + colorWheelRenderOption.targetCanvas.drawCircle(x, y, size - strokeWidth, selectorFill); + + if (currentCount >= setSize) { + colorCircleList.add(new ColorCircle(x, y, hsv)); + } else colorCircleList.get(currentCount).set(x, y, hsv); + currentCount++; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/renderer/SimpleColorWheelRenderer.java b/app/src/main/java/com/colorpicker/renderer/SimpleColorWheelRenderer.java new file mode 100644 index 0000000..b4032d0 --- /dev/null +++ b/app/src/main/java/com/colorpicker/renderer/SimpleColorWheelRenderer.java @@ -0,0 +1,45 @@ +package com.colorpicker.renderer; + +import android.graphics.Color; +import android.graphics.Paint; + +import com.colorpicker.ColorCircle; +import com.colorpicker.builder.PaintBuilder; +public class SimpleColorWheelRenderer extends AbsColorWheelRenderer { + private Paint selectorFill = PaintBuilder.newPaint().build(); + private float[] hsv = new float[3]; + + @Override + public void draw() { + final int setSize = colorCircleList.size(); + int currentCount = 0; + float half = colorWheelRenderOption.targetCanvas.getWidth() / 2f; + int density = colorWheelRenderOption.density; + float maxRadius = colorWheelRenderOption.maxRadius; + + for (int i = 0; i < density; i++) { + float p = (float) i / (density - 1); // 0~1 + float radius = maxRadius * p; + float size = colorWheelRenderOption.cSize; + int total = calcTotalCount(radius, size); + + for (int j = 0; j < total; j++) { + double angle = Math.PI * 2 * j / total + (Math.PI / total) * ((i + 1) % 2); + float x = half + (float) (radius * Math.cos(angle)); + float y = half + (float) (radius * Math.sin(angle)); + hsv[0] = (float) (angle * 180 / Math.PI); + hsv[1] = radius / maxRadius; + hsv[2] = colorWheelRenderOption.lightness; + selectorFill.setColor(Color.HSVToColor(hsv)); + selectorFill.setAlpha(getAlphaValueAsInt()); + + colorWheelRenderOption.targetCanvas.drawCircle(x, y, size - colorWheelRenderOption.strokeWidth, selectorFill); + + if (currentCount >= setSize) + colorCircleList.add(new ColorCircle(x, y, hsv)); + else colorCircleList.get(currentCount).set(x, y, hsv); + currentCount++; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/slider/AbsCustomSlider.java b/app/src/main/java/com/colorpicker/slider/AbsCustomSlider.java new file mode 100644 index 0000000..3418a77 --- /dev/null +++ b/app/src/main/java/com/colorpicker/slider/AbsCustomSlider.java @@ -0,0 +1,189 @@ +package com.colorpicker.slider; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import androidx.annotation.DimenRes; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import com.muqingbfq.R; + +public abstract class AbsCustomSlider extends View { + protected Bitmap bitmap; + protected Canvas bitmapCanvas; + protected Bitmap bar; + protected Canvas barCanvas; + protected OnValueChangedListener onValueChangedListener; + protected int barOffsetX; + protected int handleRadius = 20; + protected int barHeight = 5; + protected float value = 1; + protected boolean showBorder = false; + + private boolean inVerticalOrientation = false; + + public AbsCustomSlider(Context context) { + super(context); + init(context, null); + } + + public AbsCustomSlider(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public AbsCustomSlider(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + TypedArray styledAttrs = context.getTheme().obtainStyledAttributes( + attrs, R.styleable.AbsCustomSlider, 0, 0); + try { + inVerticalOrientation = styledAttrs.getBoolean( + R.styleable.AbsCustomSlider_inVerticalOrientation, inVerticalOrientation); + } finally { + styledAttrs.recycle(); + } + } + + protected void updateBar() { + handleRadius = getDimension(R.dimen.default_slider_handler_radius); + barHeight = getDimension(R.dimen.default_slider_bar_height); + barOffsetX = handleRadius; + + if (bar == null) + createBitmaps(); + drawBar(barCanvas); + invalidate(); + } + + protected void createBitmaps() { + int width; + int height; + if (inVerticalOrientation) { + width = getHeight(); + height = getWidth(); + } else { + width = getWidth(); + height = getHeight(); + } + + bar = Bitmap.createBitmap(Math.max(width - barOffsetX * 2, 1), barHeight, Bitmap.Config.ARGB_8888); + barCanvas = new Canvas(bar); + + if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) { + if (bitmap != null) bitmap.recycle(); + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmapCanvas = new Canvas(bitmap); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + int width; + int height; + if (inVerticalOrientation) { + width = getHeight(); + height = getWidth(); + + canvas.rotate(-90); + canvas.translate(-width, 0); + } else { + width = getWidth(); + height = getHeight(); + } + + if (bar != null && bitmapCanvas != null) { + bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + bitmapCanvas.drawBitmap(bar, barOffsetX, (height - bar.getHeight()) / 2, null); + + float x = handleRadius + value * (width - handleRadius * 2); + float y = height / 2f; + drawHandle(bitmapCanvas, x, y); + canvas.drawBitmap(bitmap, 0, 0, null); + } + } + + protected abstract void drawBar(Canvas barCanvas); + + protected abstract void onValueChanged(float value); + + protected abstract void drawHandle(Canvas canvas, float x, float y); + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + updateBar(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int width = 0; + if (widthMode == MeasureSpec.UNSPECIFIED) + width = widthMeasureSpec; + else if (widthMode == MeasureSpec.AT_MOST) + width = MeasureSpec.getSize(widthMeasureSpec); + else if (widthMode == MeasureSpec.EXACTLY) + width = MeasureSpec.getSize(widthMeasureSpec); + + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int height = 0; + if (heightMode == MeasureSpec.UNSPECIFIED) + height = heightMeasureSpec; + else if (heightMode == MeasureSpec.AT_MOST) + height = MeasureSpec.getSize(heightMeasureSpec); + else if (heightMode == MeasureSpec.EXACTLY) + height = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(width, height); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: { + if (bar != null) { + if (inVerticalOrientation) { + value = 1 - (event.getY() - barOffsetX) / bar.getWidth(); + } else { + value = (event.getX() - barOffsetX) / bar.getWidth(); + } + value = Math.max(0, Math.min(value, 1)); + onValueChanged(value); + invalidate(); + } + break; + } + case MotionEvent.ACTION_UP: { + onValueChanged(value); + if (onValueChangedListener != null) + onValueChangedListener.onValueChanged(value); + invalidate(); + } + } + return true; + } + + protected int getDimension(@DimenRes int id) { + return getResources().getDimensionPixelSize(id); + } + + public void setShowBorder(boolean showBorder) { + this.showBorder = showBorder; + } + + public void setOnValueChangedListener(OnValueChangedListener onValueChangedListener) { + this.onValueChangedListener = onValueChangedListener; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/slider/AlphaSlider.java b/app/src/main/java/com/colorpicker/slider/AlphaSlider.java new file mode 100644 index 0000000..108e64c --- /dev/null +++ b/app/src/main/java/com/colorpicker/slider/AlphaSlider.java @@ -0,0 +1,99 @@ +package com.colorpicker.slider; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.util.AttributeSet; + +import com.colorpicker.ColorPickerView; +import com.colorpicker.Utils; +import com.colorpicker.builder.PaintBuilder; +public class AlphaSlider extends AbsCustomSlider { + public int color; + private Paint alphaPatternPaint = PaintBuilder.newPaint().build(); + private Paint barPaint = PaintBuilder.newPaint().build(); + private Paint solid = PaintBuilder.newPaint().build(); + private Paint clearingStroke = PaintBuilder.newPaint().color(0xffffffff).xPerMode(PorterDuff.Mode.CLEAR).build(); + + private Paint clearStroke = PaintBuilder.newPaint().build(); + private Bitmap clearBitmap; + private Canvas clearBitmapCanvas; + + private ColorPickerView colorPicker; + + public AlphaSlider(Context context) { + super(context); + } + + public AlphaSlider(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AlphaSlider(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void createBitmaps() { + super.createBitmaps(); + alphaPatternPaint.setShader(PaintBuilder.createAlphaPatternShader(barHeight * 2)); + clearBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888); + clearBitmapCanvas = new Canvas(clearBitmap); + } + + @Override + protected void drawBar(Canvas barCanvas) { + int width = barCanvas.getWidth(); + int height = barCanvas.getHeight(); + + barCanvas.drawRect(0, 0, width, height, alphaPatternPaint); + int l = Math.max(2, width / 256); + for (int x = 0; x <= width; x += l) { + float alpha = (float) x / (width - 1); + barPaint.setColor(color); + barPaint.setAlpha(Math.round(alpha * 255)); + barCanvas.drawRect(x, 0, x + l, height, barPaint); + } + } + + @Override + protected void onValueChanged(float value) { + if (colorPicker != null) + colorPicker.setAlphaValue(value); + } + + @Override + protected void drawHandle(Canvas canvas, float x, float y) { + solid.setColor(color); + solid.setAlpha(Math.round(value * 255)); + if (showBorder) canvas.drawCircle(x, y, handleRadius, clearingStroke); + if (value < 1) { + // this fixes the same artifact issue from ColorPickerView + // happens when alpha pattern is drawn underneath a circle with the same size + clearBitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + clearBitmapCanvas.drawCircle(x, y, handleRadius * 0.75f + 4, alphaPatternPaint); + clearBitmapCanvas.drawCircle(x, y, handleRadius * 0.75f + 4, solid); + + clearStroke = PaintBuilder.newPaint().color(0xffffffff).style(Paint.Style.STROKE).stroke(6).xPerMode(PorterDuff.Mode.CLEAR).build(); + clearBitmapCanvas.drawCircle(x, y, handleRadius * 0.75f + (clearStroke.getStrokeWidth() / 2), clearStroke); + canvas.drawBitmap(clearBitmap, 0, 0, null); + } else { + canvas.drawCircle(x, y, handleRadius * 0.75f, solid); + } + } + + public void setColorPicker(ColorPickerView colorPicker) { + this.colorPicker = colorPicker; + } + + public void setColor(int color) { + this.color = color; + this.value = Utils.getAlphaPercent(color); + if (bar != null) { + updateBar(); + invalidate(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/slider/LightnessSlider.java b/app/src/main/java/com/colorpicker/slider/LightnessSlider.java new file mode 100644 index 0000000..598a234 --- /dev/null +++ b/app/src/main/java/com/colorpicker/slider/LightnessSlider.java @@ -0,0 +1,73 @@ +package com.colorpicker.slider; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.util.AttributeSet; + +import com.colorpicker.ColorPickerView; +import com.colorpicker.Utils; +import com.colorpicker.builder.PaintBuilder; +public class LightnessSlider extends AbsCustomSlider { + private int color; + private Paint barPaint = PaintBuilder.newPaint().build(); + private Paint solid = PaintBuilder.newPaint().build(); + private Paint clearingStroke = PaintBuilder.newPaint().color(0xffffffff).xPerMode(PorterDuff.Mode.CLEAR).build(); + + private ColorPickerView colorPicker; + + public LightnessSlider(Context context) { + super(context); + } + + public LightnessSlider(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LightnessSlider(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void drawBar(Canvas barCanvas) { + int width = barCanvas.getWidth(); + int height = barCanvas.getHeight(); + + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + int l = Math.max(2, width / 256); + for (int x = 0; x <= width; x += l) { + hsv[2] = (float) x / (width - 1); + barPaint.setColor(Color.HSVToColor(hsv)); + barCanvas.drawRect(x, 0, x + l, height, barPaint); + } + } + + @Override + protected void onValueChanged(float value) { + if (colorPicker != null) + colorPicker.setLightness(value); + } + + @Override + protected void drawHandle(Canvas canvas, float x, float y) { + solid.setColor(Utils.colorAtLightness(color, value)); + if (showBorder) canvas.drawCircle(x, y, handleRadius, clearingStroke); + canvas.drawCircle(x, y, handleRadius * 0.75f, solid); + } + + public void setColorPicker(ColorPickerView colorPicker) { + this.colorPicker = colorPicker; + } + + public void setColor(int color) { + this.color = color; + this.value = Utils.lightnessOfColor(color); + if (bar != null) { + updateBar(); + invalidate(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/colorpicker/slider/OnValueChangedListener.java b/app/src/main/java/com/colorpicker/slider/OnValueChangedListener.java new file mode 100644 index 0000000..eb7e029 --- /dev/null +++ b/app/src/main/java/com/colorpicker/slider/OnValueChangedListener.java @@ -0,0 +1,5 @@ +package com.colorpicker.slider; + +public interface OnValueChangedListener { + void onValueChanged(float value); +} \ No newline at end of file diff --git a/app/src/main/java/com/dirror/lyricviewx/ILyricViewX.kt b/app/src/main/java/com/dirror/lyricviewx/ILyricViewX.kt new file mode 100644 index 0000000..d0422ee --- /dev/null +++ b/app/src/main/java/com/dirror/lyricviewx/ILyricViewX.kt @@ -0,0 +1,257 @@ +package com.dirror.lyricviewx + +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.text.Layout +import androidx.annotation.ColorInt +import androidx.annotation.FloatRange +import androidx.annotation.Px +import java.io.File + +const val GRAVITY_CENTER = 0 // 居中 +const val GRAVITY_LEFT = 1 // 左 +const val GRAVITY_RIGHT = 2 // 右 + +fun Int.toLayoutAlign(): Layout.Alignment { + return when (this) { + GRAVITY_LEFT -> Layout.Alignment.ALIGN_NORMAL + GRAVITY_CENTER -> Layout.Alignment.ALIGN_CENTER + GRAVITY_RIGHT -> Layout.Alignment.ALIGN_OPPOSITE + else -> Layout.Alignment.ALIGN_CENTER + } +} + +/** + * LyricViewX 接口 + * 从 LyricViewX 提取,方便管理 + * + * @author Moriafly + * @since 2021年1月28日16:29:16 + */ +interface LyricViewXInterface { + + /** + * 设置整句之间的间隔高度 + * @param height px + */ + fun setSentenceDividerHeight(@Px height: Float) + + /** + * 设置原句与翻译之间的间隔高度 + * @param height px + */ + fun setTranslateDividerHeight(@Px height: Float) + + /** + * 设置歌词整体的垂直偏移值,配合[setHorizontalOffsetPercent]使用 + * @param offset px + * + * @see [setHorizontalOffsetPercent] + */ + fun setHorizontalOffset(@Px offset: Float) + + /** + * 设置歌词整体的垂直偏移,相对于控件高度的百分比,0.5f即表示居中,配合[setHorizontalOffset]使用 + * + * @param percent 0.0f ~ 1.0f + * + * @see [setHorizontalOffset] + */ + fun setHorizontalOffsetPercent(@FloatRange(from = 0.0, to = 1.0) percent: Float) + + /** + * 设置翻译相对与原词之间的缩放比例值 + * @param scaleValue 一般来说 0.8f 是个不错的值 + */ + fun setTranslateTextScaleValue(@FloatRange(from = 0.1, to = 2.0) scaleValue: Float) + + /** + * 设置文字的对齐方向 + */ + fun setTextGravity(gravity: Int) + + /** + * 设置非当前行歌词字体颜色 [normalColor] + */ + fun setNormalColor(@ColorInt normalColor: Int) + + /** + * 普通歌词文本字体大小 [size],单位 px + */ + fun setNormalTextSize(@Px size: Float) + + /** + * 当前歌词文本字体大小 + */ + fun setCurrentTextSize(size: Float) + + /** + * 设置当前行歌词的字体颜色 + */ + fun setCurrentColor(currentColor: Int) + + /** + * 设置拖动歌词时选中歌词的字体颜色 + */ + fun setTimelineTextColor(timelineTextColor: Int) + + /** + * 设置拖动歌词时时间线的颜色 + */ + fun setTimelineColor(timelineColor: Int) + + /** + * 设置拖动歌词时右侧时间字体颜色 + */ + fun setTimeTextColor(timeTextColor: Int) + + /** + * 设置歌词为空时屏幕中央显示的文字 [label],如“暂无歌词” + */ + fun setLabel(label: String) + + /** + * 加载歌词文本 + * 两种语言的歌词时间戳需要一致 + * + * @param mainLyricText 第一种语言歌词文本 + * @param secondLyricText 可选,第二种语言歌词文本 + */ + fun loadLyric(mainLyricText: String?, secondLyricText: String? = null) + + /** + * 加载歌词 [LyricEntry] 集合 + * 如果你在 Service 等地方自行解析歌词包装成 [LyricEntry] 集合,那么可以使用此方法载入歌词 + * + * @param lyricEntries 歌词集合 + * @since 1.3.1 + */ + fun loadLyric(lyricEntries: List) + + /** + * 刷新歌词 + * + * @param time 当前播放时间 + */ + fun updateTime(time: Long, force: Boolean = false) + + /** + * 设置歌词是否允许拖动 + * + * @param draggable 是否允许拖动 + * @param onPlayClickListener 设置歌词拖动后播放按钮点击监听器,如果允许拖动,则不能为 null + */ + fun setDraggable(draggable: Boolean, onPlayClickListener: OnPlayClickListener?) + + /** + * 设置单击 + */ + fun setOnSingerClickListener(onSingerClickListener: OnSingleClickListener?) + + /** + * 获取当前歌词每句实体,可用于歌词分享 + * + * @return LyricEntry 集合 + */ + fun getLyricEntryList(): List + + /** + * 设置当前歌词每句实体 + */ + fun setLyricEntryList(newList: List) + + /** + * 获取当前行歌词 + */ + fun getCurrentLineLyricEntry(): LyricEntry? + + /** + * 为歌词设置自定义的字体 + * + * @param file 字体文件 + */ + fun setLyricTypeface(file: File) + + /** + * 为歌词设置自定义的字体 + * + * @param path 字体文件路径 + */ + fun setLyricTypeface(path: String) + + /** + * 为歌词设置自定义的字体,可为空,若为空则应清除字体 + * + * @param typeface 字体对象 + */ + fun setLyricTypeface(typeface: Typeface?) + + /** + * 为歌词的过渡动画设置阻尼比(数值越大,回弹次数越多) + * + * @param dampingRatio 阻尼比 详见[androidx.dynamicanimation.animation.SpringForce] + */ + fun setDampingRatioForLyric(dampingRatio: Float) + + /** + * 为歌词视图的滚动动画设置阻尼比(数值越大,回弹次数越多) + * + * @param dampingRatio 阻尼比 详见[androidx.dynamicanimation.animation.SpringForce] + */ + fun setDampingRatioForViewPort(dampingRatio: Float) + + /** + * 为歌词的过渡动画设置刚度(数值越大,动画越短) + * + * @param stiffness 刚度 详见[androidx.dynamicanimation.animation.SpringForce] + */ + fun setStiffnessForLyric(stiffness: Float) + + /** + * 为歌词视图的滚动动画设置刚度(数值越大,动画越短) + * + * @param stiffness 刚度 详见[androidx.dynamicanimation.animation.SpringForce] + */ + fun setStiffnessForViewPort(stiffness: Float) + + /** + * 设置跳转播放按钮 + */ + fun setPlayDrawable(drawable: Drawable) + + /** + * 设置是否绘制歌词翻译 + */ + fun setIsDrawTranslation(isDrawTranslation: Boolean) + + /** + * 是否开启特定的模糊效果 + */ + fun setIsEnableBlurEffect(isEnableBlurEffect: Boolean) + + /** + * 设置元素的偏移百分比,0.5f即表示居中 + * + * @param itemOffsetPercent 0f ~ 1f 偏移百分比 + */ + fun setItemOffsetPercent(@FloatRange(from = 0.0, to = 1.0) itemOffsetPercent: Float) +} + +/** + * 播放按钮点击监听器,点击后应该跳转到指定播放位置 + */ +interface OnPlayClickListener { + /** + * 播放按钮被点击,应该跳转到指定播放位置 + * + * @return 是否成功消费该事件,如果成功消费,则会更新UI + */ + fun onPlayClick(time: Long): Boolean +} + +/** + * 点击歌词布局 + */ +interface OnSingleClickListener { + fun onClick() +} \ No newline at end of file diff --git a/app/src/main/java/com/dirror/lyricviewx/LyricEntry.kt b/app/src/main/java/com/dirror/lyricviewx/LyricEntry.kt new file mode 100644 index 0000000..b92499d --- /dev/null +++ b/app/src/main/java/com/dirror/lyricviewx/LyricEntry.kt @@ -0,0 +1,82 @@ +package com.dirror.lyricviewx + +import android.os.Build +import android.text.Layout +import android.text.StaticLayout +import android.text.TextPaint + +/** + * 一行歌词实体 + * @since 2021年1月19日09:51:40 Moriafly 基于 LrcEntry 改造,转换为 kt ,移除部分过时方法 + * @param time 歌词时间 + * @param text 歌词文本 + */ +class LyricEntry(@JvmField val time: Long, @JvmField val text: String) : Comparable { + + /** + * 第二文本 + */ + @JvmField + var secondText: String? = null + + /** + * staticLayout + */ + var staticLayout: StaticLayout? = null + private set + var secondStaticLayout: StaticLayout? = null + private set + + @Deprecated("存在不显示翻译的情况,会导致offset发生改变,故不再固定存储offset") + /** + * 歌词距离视图顶部的距离 + */ + var offset = Float.MIN_VALUE + + /** + * 初始化 + * @param textPaint 文本画笔 + * @param width 宽度 + * @param align 位置 + */ + fun init( + textPaint: TextPaint, + secondTextPaint: TextPaint, + width: Int, align: Layout.Alignment + ) { + staticLayout = createStaticLayout(text, textPaint, width, align) + secondStaticLayout = createStaticLayout(secondText, secondTextPaint, width, align) + offset = Float.MIN_VALUE + } + + /** + * 继承 Comparable 比较 + * @param other LyricEntry + * @return 时间差 + */ + override fun compareTo(other: LyricEntry): Int { + return (time - other.time).toInt() + } + + companion object { + fun createStaticLayout( + text: String?, + paint: TextPaint, + width: Number, + align: Layout.Alignment + ): StaticLayout? { + if (text.isNullOrEmpty()) return null + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + StaticLayout.Builder + .obtain(text, 0, text.length, paint, width.toInt()) + .setAlignment(align) + .setLineSpacing(0f, 1f) + .setIncludePad(false) + .build() + } else { + StaticLayout(text, paint, width.toInt(), align, 1f, 0f, false) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/dirror/lyricviewx/LyricUtil.kt b/app/src/main/java/com/dirror/lyricviewx/LyricUtil.kt new file mode 100644 index 0000000..cb3b4b4 --- /dev/null +++ b/app/src/main/java/com/dirror/lyricviewx/LyricUtil.kt @@ -0,0 +1,289 @@ +package com.dirror.lyricviewx + +import android.animation.ArgbEvaluator +import android.animation.ValueAnimator +import android.annotation.SuppressLint +import android.graphics.Rect +import android.text.TextUtils +import android.text.format.DateUtils +import android.view.MotionEvent +import java.io.* +import java.net.HttpURLConnection +import java.net.URL +import java.nio.charset.StandardCharsets +import java.util.* +import java.util.regex.Pattern + +/** + * 工具类 + * 原 LrcUtils 转 Kotlin + */ +object LyricUtil { + + private val PATTERN_LINE = Pattern.compile("((\\[\\d\\d:\\d\\d\\.\\d{2,3}])+)(.+)") + private val PATTERN_TIME = Pattern.compile("\\[(\\d\\d):(\\d\\d)\\.(\\d{2,3})]") + private val argbEvaluator = ArgbEvaluator() + + /** + * 从文件解析双语歌词 + */ + fun parseLrc(lrcFiles: Array?): List? { + if (lrcFiles == null || lrcFiles.size != 2 || lrcFiles[0] == null) { + return null + } + val mainLrcFile = lrcFiles[0] + val secondLrcFile = lrcFiles[1] + val mainEntryList = parseLrc(mainLrcFile) + val secondEntryList = parseLrc(secondLrcFile) + if (mainEntryList != null && secondEntryList != null) { + for (mainEntry in mainEntryList) { + for (secondEntry in secondEntryList) { + if (mainEntry.time == secondEntry.time) { + mainEntry.secondText = secondEntry.text + } + } + } + } + return mainEntryList + } + + /** + * 从文件解析歌词 + */ + private fun parseLrc(lrcFile: File?): List? { + if (lrcFile == null || !lrcFile.exists()) { + return null + } + val entryList: MutableList = ArrayList() + try { + val br = + BufferedReader(InputStreamReader(FileInputStream(lrcFile), StandardCharsets.UTF_8)) + var line: String + while (br.readLine().also { line = it } != null) { + val list = parseLine(line) + if (list != null && list.isNotEmpty()) { + entryList.addAll(list) + } + } + br.close() + } catch (e: IOException) { + e.printStackTrace() + } + entryList.sort() + return entryList + } + + /** + * 从文本解析双语歌词 + */ + fun parseLrc(lrcTexts: Array?): List? { + if (lrcTexts == null || lrcTexts.size != 2 || TextUtils.isEmpty(lrcTexts[0])) { + return null + } + val mainLrcText = lrcTexts[0] + val secondLrcText = lrcTexts[1] + val mainEntryList = mainLrcText?.let { parseLrc(it) } + + /** + * 当输入的secondLrcText为空时,按如下格式解析歌词 + * (音乐标签下载的第二种歌词格式) + * + * [00:21.11]いつも待ち合わせより15分前集合 + * [00:21.11]总会比相约时间早15分钟集合 + * [00:28.32]駅の改札ぬける + * [00:28.32]穿过车站的检票口 + * [00:31.39]ざわめきにわくわくだね + * [00:31.39]嘈杂声令内心兴奋不已 + * [00:35.23]どこへ向かうかなんて + * [00:35.23]不在意接下来要去哪里 + */ + if (TextUtils.isEmpty(secondLrcText)) { + var lastEntry: LyricEntry? = null + return mainEntryList?.filter { now -> + if (lastEntry == null) { + lastEntry = now + return@filter true + } + + if (lastEntry!!.time == now.time) { + lastEntry!!.secondText = now.text + lastEntry = null + return@filter false + } + + lastEntry = now + true + } + } + + val secondEntryList = secondLrcText?.let { parseLrc(it) } + if (mainEntryList != null && secondEntryList != null) { + for (mainEntry in mainEntryList) { + for (secondEntry in secondEntryList) { + if (mainEntry.time == secondEntry.time) { + mainEntry.secondText = secondEntry.text + } + } + } + } + return mainEntryList + } + + /** + * 从文本解析歌词 + */ + private fun parseLrc(lrcText: String): List? { + var lyricText = lrcText.trim() + if (TextUtils.isEmpty(lyricText)) return null + + if (lyricText.startsWith("\uFEFF")) { + lyricText = lyricText.replace("\uFEFF", "") + } + + // 针对传入 Language="Media Monkey Format"; Lyrics="......"; 的情况 + lyricText = lyricText.substringAfter("Lyrics=\"") + .substringBeforeLast("\";") + + val entryList: MutableList = ArrayList() + val array = lyricText.split("\\n".toRegex()).toTypedArray() + for (line in array) { + val list = parseLine(line) + if (!list.isNullOrEmpty()) { + entryList.addAll(list) + } + } + entryList.sort() + return entryList + } + + /** + * 获取网络文本,需要在工作线程中执行 + */ + fun getContentFromNetwork(url: String?, charset: String?): String? { + var lrcText: String? = null + try { + val url = URL(url) + val conn = url.openConnection() as HttpURLConnection + conn.requestMethod = "GET" + conn.connectTimeout = 10000 + conn.readTimeout = 10000 + if (conn.responseCode == 200) { + val `is` = conn.inputStream + val bos = ByteArrayOutputStream() + val buffer = ByteArray(1024) + var len: Int + while (`is`.read(buffer).also { len = it } != -1) { + bos.write(buffer, 0, len) + } + `is`.close() + bos.close() + lrcText = bos.toString(charset) + } + } catch (e: Exception) { + e.printStackTrace() + } + return lrcText + } + + /** + * 解析一行歌词 + */ + private fun parseLine(line: String): List? { + var lyricLine = line + if (TextUtils.isEmpty(lyricLine)) { + return null + } + lyricLine = lyricLine.trim { it <= ' ' } + // [00:17.65]让我掉下眼泪的 + val lineMatcher = PATTERN_LINE.matcher(lyricLine) + if (!lineMatcher.matches()) { + return null + } + val times = lineMatcher.group(1)!! + val text = lineMatcher.group(3)!! + val entryList: MutableList = ArrayList() + + // [00:17.65] + val timeMatcher = PATTERN_TIME.matcher(times) + while (timeMatcher.find()) { + val min = timeMatcher.group(1)!!.toLong() + val sec = timeMatcher.group(2)!!.toLong() + val milString = timeMatcher.group(3)!! + var mil = milString.toLong() + // 如果毫秒是两位数,需要乘以 10,when 新增支持 1 - 6 位毫秒,很多获取的歌词存在不同的毫秒位数 + when (milString.length) { + 1 -> mil *= 100 + 2 -> mil *= 10 + 4 -> mil /= 10 + 5 -> mil /= 100 + 6 -> mil /= 1000 + } + val time = min * DateUtils.MINUTE_IN_MILLIS + sec * DateUtils.SECOND_IN_MILLIS + mil + entryList.add(LyricEntry(time, text)) + } + return entryList + } + + /** + * 转为[分:秒] + */ + fun formatTime(milli: Long): String { + val m = (milli / DateUtils.MINUTE_IN_MILLIS).toInt() + val s = (milli / DateUtils.SECOND_IN_MILLIS % 60).toInt() + val mm = String.format(Locale.getDefault(), "%02d", m) + val ss = String.format(Locale.getDefault(), "%02d", s) + return "$mm:$ss" + } + + /** + * BUG java.lang.NoSuchFieldException: No field sDurationScale in class Landroid/animation/ValueAnimator; #3 + */ + @SuppressLint("SoonBlockedPrivateApi") + @Deprecated("") + fun resetDurationScale() { + try { + val mField = ValueAnimator::class.java.getDeclaredField("sDurationScale") + mField.isAccessible = true + mField.setFloat(null, 1f) + } catch (e: Exception) { + e.printStackTrace() + } + } + + /** + * 结合fraction,计算两个值之间的比例 + */ + fun calcScaleValue(a: Float, b: Float, f: Float, reverse: Boolean = false): Float { + if (b == 0f) return 1f + return 1f + ((a - b) / b) * (if (reverse) 1f - f else f) + } + + /** + * 颜色值插值函数 + */ + fun lerpColor(a: Int, b: Int, f: Float): Int { + return argbEvaluator.evaluate(f, a, b) as Int + } + + /** + * 简单的插值函数 + */ + fun lerp(from: Float, to: Float, fraction: Float): Float { + return from + (to - from) * fraction + } + + /** + * 判断MotionEvent是否发生在Rect中 + */ + fun MotionEvent.insideOf(rect: Rect?): Boolean { + rect ?: return false + return rect.contains(x.toInt(), y.toInt()) + } + + fun normalize(min: Float, max: Float, value: Float, limit: Boolean = false): Float { + if (min == max) return 1f + return ((value - min) / (max - min)).let { + if (limit) it.coerceIn(0f, 1f) else it + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dirror/lyricviewx/LyricViewX.kt b/app/src/main/java/com/dirror/lyricviewx/LyricViewX.kt new file mode 100644 index 0000000..d967af5 --- /dev/null +++ b/app/src/main/java/com/dirror/lyricviewx/LyricViewX.kt @@ -0,0 +1,1098 @@ +package com.dirror.lyricviewx +import android.animation.ValueAnimator +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.os.Looper +import android.text.Layout +import android.text.StaticLayout +import android.text.TextPaint +import android.text.format.DateUtils +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.MotionEvent +import android.widget.Scroller +import androidx.annotation.FloatRange +import androidx.core.content.ContextCompat +import androidx.dynamicanimation.animation.SpringForce +import androidx.dynamicanimation.animation.springAnimationOf +import androidx.dynamicanimation.animation.withSpringForceProperties +import com.dirror.lyricviewx.LyricUtil.calcScaleValue +import com.dirror.lyricviewx.LyricUtil.formatTime +import com.dirror.lyricviewx.LyricUtil.insideOf +import com.dirror.lyricviewx.LyricUtil.lerp +import com.dirror.lyricviewx.LyricUtil.lerpColor +import com.dirror.lyricviewx.LyricUtil.normalize +import com.dirror.lyricviewx.extension.BlurMaskFilterExt +import com.lalilu.easeview.EaseView +import com.lalilu.easeview.animatevalue.BoolValue +import com.lalilu.easeview.animatevalue.FloatListAnimateValue +import com.muqingbfq.R +import java.io.File +import kotlin.concurrent.thread +import kotlin.math.abs +import kotlin.math.max +open class LyricViewX : EaseView, LyricViewXInterface { + constructor(context: Context) : super(context) { + init(null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(attrs) + } + + companion object { + private const val TAG = "LyricViewX" + // 时间线持续时间 + private const val TIMELINE_KEEP_TIME = 3 * DateUtils.SECOND_IN_MILLIS + + /** 单句歌词集合 */ + @JvmField + val lyricEntryList: MutableList = ArrayList() + + @JvmStatic + fun lrc(a: String?, b: String?) { + lyricEntryList.clear() + val lrcEntries = LyricUtil.parseLrc(arrayOf(a, b)) + if (!lrcEntries.isNullOrEmpty()) { + lyricEntryList.addAll(lrcEntries) + } + lyricEntryList.sort() + } + } + + private val readyHelper = ReadyHelper() + private val blurMaskFilterExt = BlurMaskFilterExt() + + + /** 主歌词画笔 */ + private val lyricPaint = TextPaint() + + /** 副歌词(一般为翻译歌词)画笔 */ + private val secondLyricPaint = TextPaint() + + /** 时间文字画笔 */ + private val timePaint = TextPaint() + + private var timeFontMetrics: Paint.FontMetrics? = null + + /** 跳转播放按钮 */ + private var playDrawable: Drawable? = null + + private var translateDividerHeight = 0f + private var sentenceDividerHeight = 0f + private var animationDuration: Long = 0 + private var normalTextColor = 0 + private var normalTextSize = 0f + private var currentTextColor = 0 + private var currentTextSize = 0f + private var translateTextScaleValue = 1f + private var timelineTextColor = 0 + private var timelineColor = 0 + private var timeTextColor = 0 + private var drawableWidth = 0 + private var timeTextWidth = 0 + private var defaultLabel: String? = null + private var lrcPadding = 0f + private var onPlayClickListener: OnPlayClickListener? = null + private var onSingerClickListener: OnSingleClickListener? = null + private var animator: ValueAnimator? = null + private var gestureDetector: GestureDetector? = null + private var scroller: Scroller? = null + private var flag: Any? = null + private var isTouching = false + private var isFling = false + private var textGravity = GRAVITY_CENTER // 歌词显示位置,靠左 / 居中 / 靠右 + private var horizontalOffset: Float = 0f + private var horizontalOffsetPercent: Float = 0.5f + private var itemOffsetPercent: Float = 0.5f + private var dampingRatioForLyric: Float = SpringForce.DAMPING_RATIO_LOW_BOUNCY + private var dampingRatioForViewPort: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY + private var stiffnessForLyric: Float = SpringForce.STIFFNESS_LOW + private var stiffnessForViewPort: Float = SpringForce.STIFFNESS_VERY_LOW + + private var currentLine = 0 // 当前高亮显示的歌词 + private val focusLine: Int // 当前焦点歌词 + get() = if (isTouching || isFling) centerLine else currentLine + + /** + * 获取当前在视图中央的行数 + */ + private val centerLine: Int + get() { + var centerLine = 0 + var minDistance = Float.MAX_VALUE + var tempDistance: Float + + for (i in lyricEntryList.indices) { + tempDistance = abs(mViewPortOffset - getOffset(i)) + if (tempDistance < minDistance) { + minDistance = tempDistance + centerLine = i + } + } + return centerLine + } + + /** + * 获取歌词宽度 + */ + open val lrcWidth: Float + get() = width - lrcPadding * 2 + + /** + * 歌词整体的垂直偏移值 + */ + open val startOffset: Float + get() = height.toFloat() * horizontalOffsetPercent + horizontalOffset + + + /** + * 原有的mOffset被拆分成两个独立的offset,这样可以更好地让进度和拖拽滚动独立开来 + */ + private var mCurrentOffset = 0f // 实际的歌词进度Offset + private var mViewPortOffset = 0f // 歌词显示窗口的Offset + + private var animateProgress = 0f // 动画进度 + private var animateTargetOffset = 0f // 动画目标Offset + private var animateStartOffset = 0f // 动画起始Offset + + private val viewPortSpringAnimator = springAnimationOf( + getter = { mViewPortOffset }, + setter = { value -> + if (!isShowTimeline.value && !isTouching && !isFling) { + mViewPortOffset = value + invalidate() + } + } + ).withSpringForceProperties { + dampingRatio = dampingRatioForViewPort + stiffness = stiffnessForViewPort + finalPosition = 0f + } + + /** + * 弹性动画Scroller + */ + private val progressSpringAnimator = springAnimationOf( + getter = { mCurrentOffset }, + setter = { value -> + animateProgress = normalize(animateStartOffset, animateTargetOffset, value) + mCurrentOffset = value + + if (!isShowTimeline.value && !isTouching && !isFling) { + viewPortSpringAnimator.animateToFinalPosition(animateTargetOffset) + } + invalidate() + } + ).withSpringForceProperties { + dampingRatio = dampingRatioForLyric + stiffness = stiffnessForLyric + finalPosition = 0f + } + + @SuppressLint("CustomViewStyleable") + private fun init(attrs: AttributeSet?) { + readyHelper.readyState = STATE_INITIALIZING + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LyricView) + currentTextSize = typedArray.getDimension(R.styleable.LyricView_lrcTextSize, resources.getDimension(R.dimen.lrc_text_size)) + normalTextSize = typedArray.getDimension(R.styleable.LyricView_lrcNormalTextSize, resources.getDimension(R.dimen.lrc_text_size)) + if (normalTextSize == 0f) { + normalTextSize = currentTextSize + } + + sentenceDividerHeight = + typedArray.getDimension(R.styleable.LyricView_lrcSentenceDividerHeight, resources.getDimension(R.dimen.lrc_sentence_divider_height)) + translateDividerHeight = + typedArray.getDimension(R.styleable.LyricView_lrcTranslateDividerHeight, resources.getDimension(R.dimen.lrc_translate_divider_height)) + val defDuration = resources.getInteger(R.integer.lrc_animation_duration) + animationDuration = typedArray.getInt(R.styleable.LyricView_lrcAnimationDuration, defDuration).toLong() + animationDuration = + if (animationDuration < 0) defDuration.toLong() else animationDuration + + normalTextColor = typedArray.getColor( + R.styleable.LyricView_lrcNormalTextColor, + ContextCompat.getColor(context, R.color.lrc_normal_text_color) + ) + currentTextColor = typedArray.getColor( + R.styleable.LyricView_lrcCurrentTextColor, + ContextCompat.getColor(context, R.color.lrc_current_text_color) + ) + timelineTextColor = typedArray.getColor( + R.styleable.LyricView_lrcTimelineTextColor, + ContextCompat.getColor(context, R.color.lrc_timeline_text_color) + ) + defaultLabel = typedArray.getString(R.styleable.LyricView_lrcLabel) + defaultLabel = if (defaultLabel.isNullOrEmpty()) "暂无歌词" else defaultLabel + lrcPadding = typedArray.getDimension(R.styleable.LyricView_lrcPadding, 0f) + timelineColor = typedArray.getColor( + R.styleable.LyricView_lrcTimelineColor, + ContextCompat.getColor(context, R.color.lrc_timeline_color) + ) + val timelineHeight = typedArray.getDimension( + R.styleable.LyricView_lrcTimelineHeight, + resources.getDimension(R.dimen.lrc_timeline_height) + ) + playDrawable = typedArray.getDrawable(R.styleable.LyricView_lrcPlayDrawable) + playDrawable = if (playDrawable == null) ContextCompat.getDrawable( + context, + R.drawable.lrc_play + ) else playDrawable + timeTextColor = typedArray.getColor( + R.styleable.LyricView_lrcTimeTextColor, + ContextCompat.getColor(context, R.color.lrc_time_text_color) + ) + val timeTextSize = typedArray.getDimension( + R.styleable.LyricView_lrcTimeTextSize, + resources.getDimension(R.dimen.lrc_time_text_size) + ) + textGravity = typedArray.getInteger(R.styleable.LyricView_lrcTextGravity, GRAVITY_CENTER) + translateTextScaleValue = typedArray.getFloat(R.styleable.LyricView_lrcTranslateTextScaleValue, 1f) + horizontalOffset = typedArray.getDimension(R.styleable.LyricView_lrcHorizontalOffset, 0f) + horizontalOffsetPercent = typedArray.getDimension(R.styleable.LyricView_lrcHorizontalOffsetPercent, 0.5f) + itemOffsetPercent = typedArray.getDimension(R.styleable.LyricView_lrcItemOffsetPercent, 0.5f) + isDrawTranslation = typedArray.getBoolean(R.styleable.LyricView_lrcIsDrawTranslation, false) + typedArray.recycle() + drawableWidth = resources.getDimension(R.dimen.lrc_drawable_width).toInt() + timeTextWidth = resources.getDimension(R.dimen.lrc_time_width).toInt() + lyricPaint.isAntiAlias = true + lyricPaint.textSize = currentTextSize + lyricPaint.textAlign = Paint.Align.LEFT +// lyricPaint.setShadowLayer(0.1f, 0f, 1f, Color.DKGRAY) + secondLyricPaint.isAntiAlias = true + secondLyricPaint.textSize = currentTextSize + secondLyricPaint.textAlign = Paint.Align.LEFT +// secondLyricPaint.setShadowLayer(0.1f, 0f, 1f, Color.DKGRAY) + timePaint.isAntiAlias = true + timePaint.textSize = timeTextSize + timePaint.textAlign = Paint.Align.CENTER + timePaint.strokeWidth = timelineHeight + timePaint.strokeCap = Paint.Cap.ROUND + timeFontMetrics = timePaint.fontMetrics + gestureDetector = GestureDetector(context, mSimpleOnGestureListener) + gestureDetector!!.setIsLongpressEnabled(false) + scroller = Scroller(context) + } + + /** + * 歌词是否有效 + * @return true,如果歌词有效,否则false + */ + private fun hasLrc(): Boolean { + return lyricEntryList.isNotEmpty() + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + if (changed) { + initPlayDrawable() + initEntryList() + if (hasLrc()) { + smoothScrollTo(currentLine) + } + } + readyHelper.readyState = STATE_INITIALIZED + } + + private val isShowTimeline = BoolValue().also(::registerValue) + private val isEnableBlurEffect = BoolValue().also(::registerValue) + private val progressKeeper = FloatListAnimateValue().also(::registerValue) + private val blurProgressKeeper = FloatListAnimateValue().also(::registerValue) + + private val heightKeeper = LinkedHashMap() + private val offsetKeeper = LinkedHashMap() + private val minOffsetKeeper = LinkedHashMap() + private val maxOffsetKeeper = LinkedHashMap() + + private var viewPortStartOffset: Float = 0f + private var isDrawTranslationValue = 0f + private var isDrawTranslation: Boolean = false + set(value) { + if (field == value) return + field = value + viewPortStartOffset = mViewPortOffset + isDrawTranslationAnimator.animateToFinalPosition(if (value) 1000f else 0f) + } + private val isDrawTranslationAnimator = springAnimationOf( + getter = { isDrawTranslationValue * 1000f }, + setter = { + isDrawTranslationValue = it / 1000f + + if (!isTouching && !isFling) { + viewPortSpringAnimator.cancel() + + val targetOffset = if (isDrawTranslation) getMaxOffset(focusLine) else getMinOffset(focusLine) + val animateValue = if (isDrawTranslation) isDrawTranslationValue else 1f - isDrawTranslationValue + + mViewPortOffset = lerp(viewPortStartOffset, targetOffset, animateValue) + } + invalidate() + }, + ).withSpringForceProperties { + dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY + stiffness = SpringForce.STIFFNESS_LOW + finalPosition = if (isDrawTranslation) 1000f else 0f + } + + override fun onPreDraw(canvas: Canvas): Boolean { + // 无歌词,只渲染一句无歌词的提示语句 + if (!hasLrc()) { + lyricPaint.color = currentTextColor + lyricPaint.textSize = normalTextSize + LyricEntry.createStaticLayout( + defaultLabel, + lyricPaint, + lrcWidth, + Layout.Alignment.ALIGN_CENTER + )?.let { + drawText( + canvas = canvas, + staticLayout = it, + calcHeightOnly = false, + yOffset = startOffset, + yClipPercentage = 1f + ) + } + return false + } + return super.onPreDraw(canvas) + } + + override fun onDoDraw(canvas: Canvas): Boolean { + val centerY = startOffset + val currentCenterLine = centerLine + + // 当显示时间线时,需要绘制时间线 + if (isShowTimeline.value || isShowTimeline.animateValue > 0f) { + val alpha = (isShowTimeline.animateValue * 255f).toInt() + + // 绘制播放按钮 + playDrawable?.let { + it.alpha = alpha + it.draw(canvas) + } + + // 绘制时间线 + timePaint.color = timelineColor + timePaint.alpha = alpha + canvas.drawLine( + timeTextWidth.toFloat(), centerY, + (width - timeTextWidth).toFloat(), centerY, timePaint + ) + + // 绘制当前时间 + val timeText = formatTime(lyricEntryList[currentCenterLine].time) + val timeX = width - timeTextWidth.toFloat() / 2 + val timeY = centerY - (timeFontMetrics!!.descent + timeFontMetrics!!.ascent) / 2 + timePaint.color = timeTextColor + timePaint.alpha = alpha + canvas.drawText(timeText, timeX, timeY, timePaint) + } + + canvas.translate(0f, mViewPortOffset) + + var yOffset = 0f + var yMinOffset = 0f + var yMaxOffset = 0f + var scaleValue: Float + var progress: Float + var radius: Int + var calcHeightOnly: Boolean + + for (i in lyricEntryList.indices) { + // 根据上一项所计算得到的offset值,判断当前元素是否在需要绘制的区间,如果不在,则只需要计算高度不进行绘制相关计算 + calcHeightOnly = getOffset(i - 1) !in (mViewPortOffset - height)..(mViewPortOffset + height) + progressKeeper.updateTargetValue(i, if (currentLine == i) animateProgress else 0f) + progress = progressKeeper.getValueByIndex(i) + scaleValue = 1f + radius = 0 + + if (!calcHeightOnly) { + when { + // 当前行动画未结束 + progress > 0f -> { + scaleValue = calcScaleValue(currentTextSize, normalTextSize, progress) + lyricPaint.color = lerpColor(normalTextColor, currentTextColor, progress.coerceIn(0f, 1f)) + } + + isShowTimeline.value && i == currentCenterLine -> { + lyricPaint.color = timelineTextColor + } + + else -> { + lyricPaint.color = normalTextColor + } + } + lyricPaint.textSize = normalTextSize + secondLyricPaint.textSize = lyricPaint.textSize * translateTextScaleValue + secondLyricPaint.color = lyricPaint.color + + if (isEnableBlurEffect.value || isEnableBlurEffect.animateValue > 0f) { + radius = when (i) { + currentCenterLine -> 0 + currentCenterLine + 1 -> 3 + currentCenterLine + 2, currentCenterLine - 1 -> 7 + currentCenterLine + 3, currentCenterLine - 2 -> 11 + currentCenterLine + 4, currentCenterLine - 3 -> 20 + else -> 20 + } + blurProgressKeeper.updateTargetValue(i, radius.toFloat()) + radius = blurProgressKeeper.getValueByIndex(i).toInt() + radius = (radius * isEnableBlurEffect.animateValue).toInt() + } + } + + val itemHeight = drawLyricEntry( + canvas = canvas, + entry = lyricEntryList[i], + calcHeightOnly = calcHeightOnly, + yOffset = yOffset, + scaleValue = scaleValue, + blurRadius = radius, + ) { minHeight, maxHeight -> + minOffsetKeeper[i] = yMinOffset + calcOffsetOfItem(minHeight, sentenceDividerHeight) + yMinOffset += minHeight + + maxOffsetKeeper[i] = yMaxOffset + calcOffsetOfItem(maxHeight, sentenceDividerHeight) + yMaxOffset += maxHeight + } + heightKeeper[i] = itemHeight + offsetKeeper[i] = yOffset + calcOffsetOfItem(itemHeight, sentenceDividerHeight) + yOffset += itemHeight + } + return super.onDoDraw(canvas) + } + + /** + * 画一组歌词语句 + * + * @param calcHeightOnly 是否只计算高度 + * @param yOffset 歌词中心 Y 坐标 + * @param scaleValue 缩放比例 + * @param blurRadius 模糊半径 + * + * @return 该组歌词的实际绘制高度 + */ + private fun drawLyricEntry( + canvas: Canvas, + entry: LyricEntry, + calcHeightOnly: Boolean, + yOffset: Float, + scaleValue: Float, + blurRadius: Int, + callback: (minHeight: Float, maxHeight: Float) -> Unit = { _, _ -> } + ): Float { + var tempHeight = 0f + var minTempHeight = 0f + var maxTempHeight = 0f + + entry.staticLayout?.let { + tempHeight += drawText( + canvas = canvas, + staticLayout = it, + calcHeightOnly = calcHeightOnly, + yOffset = yOffset, + yClipPercentage = 1f, + scale = scaleValue, + blurRadius = blurRadius + ) + minTempHeight = tempHeight + maxTempHeight = tempHeight + + entry.secondStaticLayout?.let { second -> + tempHeight += translateDividerHeight * isDrawTranslationValue + maxTempHeight += translateDividerHeight + + tempHeight += drawText( + canvas = canvas, + staticLayout = second, + calcHeightOnly = calcHeightOnly, + yOffset = yOffset + tempHeight, + yClipPercentage = isDrawTranslationValue, + alpha = isDrawTranslationValue, + scale = scaleValue, + blurRadius = blurRadius + ) { _, max -> + maxTempHeight += max + } + } + tempHeight += sentenceDividerHeight + minTempHeight += sentenceDividerHeight + maxTempHeight += sentenceDividerHeight + } + callback(minTempHeight, maxTempHeight) + return tempHeight + } + + /** + * 画一行歌词 + * + * @param calcHeightOnly 是否只计算高度 + * @param yOffset 歌词中心 Y 坐标 + * @param yClipPercentage 垂直裁剪比例 + * @param scale 缩放比例 + * @param alpha 透明度 + * @param blurRadius 模糊半径 实现类似AppleMusic的歌词语句的模糊效果 + * + * @return 实际绘制高度 + */ + private fun drawText( + canvas: Canvas, + staticLayout: StaticLayout, + calcHeightOnly: Boolean = false, + yOffset: Float, + @FloatRange(from = 0.0, to = 1.0) + yClipPercentage: Float = 1f, + scale: Float = 1f, + alpha: Float = 1f, + blurRadius: Int = 0, + callback: (minHeight: Float, maxHeight: Float) -> Unit = { _, _ -> } + ): Float { + if (staticLayout.lineCount == 0) { + callback(0f, 0f) + return 0f + } + if (calcHeightOnly) { + callback(0f, staticLayout.height.toFloat()) + return staticLayout.height * yClipPercentage + } + val lineHeight = staticLayout.height.toFloat() / staticLayout.lineCount.toFloat() + + var yTemp = 0f // y轴临时偏移量 + var pivotYTemp: Float // 缩放中心Y坐标 + var itemActualHeight: Float // 单行实际绘制高度 + var actualHeight = 0f // 实际绘制高度 + + staticLayout.paint.alpha = (alpha * 255f).toInt() + staticLayout.paint.maskFilter = blurMaskFilterExt.get(blurRadius) + + /** + * 由于对StaticLayout整个缩放会使其中间的行间距也被缩放(通过TextPaint的textSize缩放则不会), + * 导致其真实渲染高度大于StaticLayout的height属性的值,同时也没有其他的接口能实现相同的缩放效果(对TextSize缩放会显得卡卡的) + * + * 所以通过Canvas的clipRect,来分别对StaticLayout的每一行文字进行缩放和绘制(StaticLayout的各行高度是一致的) + */ + repeat(staticLayout.lineCount) { + itemActualHeight = lineHeight * yClipPercentage + pivotYTemp = yTemp + itemActualHeight - staticLayout.paint.descent() // TextPaint修改textSize所实现的缩放效果应该就是descent线上的缩放(感觉效果差不多) + + canvas.save() + canvas.translate(lrcPadding, yOffset) + canvas.clipRect(-lrcPadding, yTemp, staticLayout.width.toFloat() + lrcPadding, yTemp + itemActualHeight) + + // 根据文字的gravity设置缩放基点坐标 + when (textGravity) { + GRAVITY_LEFT -> canvas.scale(scale, scale, 0f, pivotYTemp) + GRAVITY_RIGHT -> { + canvas.scale(scale, scale, staticLayout.width.toFloat(), pivotYTemp) + } + + GRAVITY_CENTER -> { + canvas.scale(scale, scale, staticLayout.width / 2f, pivotYTemp) + } + } + staticLayout.draw(canvas) + canvas.restore() + yTemp += itemActualHeight + actualHeight += itemActualHeight + } + callback(0f, staticLayout.height.toFloat()) + return actualHeight + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) { + isTouching = false + if (hasLrc() && !isFling) { + // TODO 应该为Timeline独立设置一个Enable开关, 这样就可以不需要等待Timeline消失 + postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME) + } + } + return gestureDetector!!.onTouchEvent(event) + } + + /** + * 手势监听器 + */ + private val mSimpleOnGestureListener: SimpleOnGestureListener = + object : SimpleOnGestureListener() { + + override fun onDown(e: MotionEvent): Boolean { + // 有歌词并且设置了 mOnPlayClickListener + if (onPlayClickListener != null) { + scroller!!.forceFinished(true) + removeCallbacks(hideTimelineRunnable) + isTouching = true + invalidate() + return true + } + return super.onDown(e) + } + + override fun onScroll( + e1: MotionEvent, + e2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + if (hasLrc()) { + // 如果没显示 Timeline 的时候,distanceY 一段距离后再显示时间线 + if (!isShowTimeline.value && abs(distanceY) >= 10) { + // 滚动显示时间线 + isShowTimeline.value = true + } + mViewPortOffset += -distanceY + mViewPortOffset.coerceIn(getOffset(lyricEntryList.size - 1), getOffset(0)) + invalidate() + return true + } + return super.onScroll(e1, e2, distanceX, distanceY) + } + + fun onFling( + e1: MotionEvent?, + e2: MotionEvent?, + velocityX: Float?, + velocityY: Float? + ): Boolean { + if (hasLrc()) { + scroller!!.fling( + 0, mViewPortOffset.toInt(), 0, + velocityY!!.toInt(), 0, 0, + getOffset(lyricEntryList.size - 1).toInt(), + getOffset(0).toInt() + ) + isFling = true + return true + } + return super.onFling(e1!!, e2!!, velocityX!!, velocityY!!) + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + if (!hasLrc() || !isShowTimeline.value || !e.insideOf(playDrawable?.bounds)) { + onSingerClickListener?.onClick() + return super.onSingleTapConfirmed(e) + } + + val centerLine = centerLine + val centerLineTime = lyricEntryList[centerLine].time + // onPlayClick 消费了才更新 UI + if (onPlayClickListener?.onPlayClick(centerLineTime) == true) { + isShowTimeline.value = false + removeCallbacks(hideTimelineRunnable) + smoothScrollTo(centerLine) + invalidate() + return true + } + return super.onSingleTapConfirmed(e) + } + } + + private val hideTimelineRunnable = Runnable { + if (hasLrc() && isShowTimeline.value) { + isShowTimeline.value = false + smoothScrollTo(currentLine) + } + } + + override fun computeScroll() { + if (scroller!!.computeScrollOffset()) { + mViewPortOffset = scroller!!.currY.toFloat() + invalidate() + } + if (isFling && scroller!!.isFinished) { + isFling = false + if (hasLrc() && !isTouching) { + adjustCenter() + postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME) + } + } + } + + override fun onDetachedFromWindow() { + removeCallbacks(hideTimelineRunnable) + super.onDetachedFromWindow() + } + + private fun onLrcLoaded(entryList: List?) { + if (!entryList.isNullOrEmpty()) { + lyricEntryList.addAll(entryList) + } + lyricEntryList.sort() + initEntryList() + invalidate() + } + + private fun initPlayDrawable() { + val l = (timeTextWidth - drawableWidth) / 2 + val t = startOffset.toInt() - drawableWidth / 2 + val r = l + drawableWidth + val b = t + drawableWidth + playDrawable!!.setBounds(l, t, r, b) + } + + private fun initEntryList() { + if (!hasLrc() || width == 0) { + return + } + /** + * StaticLayout 根据初始化时传入的 TextSize 计算换行的位置 + * 如果 [currentTextSize] 与 [normalTextSize] 相差较大, + * 则会导致歌词渲染时溢出边界,或行间距不足挤压在一起 + * + * 故计算出可能的最大 TextSize 以后,用其初始化,使 StaticLayout 拥有足够的高度 + */ + lyricPaint.textSize = max(currentTextSize, normalTextSize) + secondLyricPaint.textSize = lyricPaint.textSize * translateTextScaleValue + for (lrcEntry in lyricEntryList) { + lrcEntry.init( + lyricPaint, secondLyricPaint, + lrcWidth.toInt(), textGravity.toLayoutAlign() + ) + } + mCurrentOffset = startOffset + mViewPortOffset = startOffset + } + + private fun reset() { + // TODO 待完善reset的逻辑 + scroller!!.forceFinished(true) + isShowTimeline.value = false + isTouching = false + isFling = false + removeCallbacks(hideTimelineRunnable) + lyricEntryList.clear() + mCurrentOffset = 0f + mViewPortOffset = 0f + currentLine = 0 + invalidate() + } + + /** + * 将中心行微调至正中心 + */ + private fun adjustCenter() { + smoothScrollTo(currentLine) + } + + /** + * 平滑滚动过渡到某一行 + * + * @param line 行号 + */ + private fun smoothScrollTo(line: Int) { + val offset = getOffset(line) + animateStartOffset = mCurrentOffset + animateTargetOffset = offset + progressSpringAnimator.animateToFinalPosition(offset) + } + + /** + * 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数) + */ + private fun findShowLine(time: Long): Int { + var left = 0 + var right = lyricEntryList.size + while (left <= right) { + val middle = (left + right) / 2 + val middleTime = lyricEntryList[middle].time + if (time < middleTime) { + right = middle - 1 + } else { + if (middle + 1 >= lyricEntryList.size || time < lyricEntryList[middle + 1].time) { + return middle + } + left = middle + 1 + } + } + return 0 + } + + /** + * 计算单个歌词元素的偏移量,用于控制歌词对其中线的位置 + * + * 计算出来的歌词高度包含了分割线的高度,所以需要减去分割线的高度 + * + * @param itemHeight 歌词元素的高度 + * @param dividerHeight 分割线的高度 + * + * @return 歌词元素的偏移量 + */ + protected open fun calcOffsetOfItem(itemHeight: Float, dividerHeight: Float): Float { + return (itemHeight - dividerHeight) * itemOffsetPercent + } + + /** + * 因为添加了 [translateDividerHeight] 用来间隔开歌词与翻译, + * 所以直接从 [LyricEntry] 获取高度不可行, + * 故使用该 [getLyricHeight] 方法来计算 [LyricEntry] 的高度 + */ + @Deprecated("不再单独计算歌词的高度,在绘制时计算并进行更新缓存,所见即所得") + open fun getLyricHeight(line: Int): Int { + var height = lyricEntryList[line].staticLayout?.height ?: return 0 + lyricEntryList[line].secondStaticLayout?.height?.let { + height += (it + translateDividerHeight).toInt() + } + return height + } + + /** + * 获取歌词距离视图顶部的距离 + */ + private fun getOffset(line: Int): Float { + return startOffset - (offsetKeeper[line] ?: 0f) + } + + private fun getMinOffset(line: Int): Float { + return startOffset - (minOffsetKeeper[line] ?: 0f) + } + + private fun getMaxOffset(line: Int): Float { + return startOffset - (maxOffsetKeeper[line] ?: 0f) + } + + /** + * 在主线程中运行 + */ + private fun runOnMain(r: Runnable) { + if (Looper.myLooper() == Looper.getMainLooper()) { + r.run() + } else { + post(r) + } + } + + /** + * 以下是公共部分 + * 用法见接口 [LyricViewXInterface] + */ + + override fun setSentenceDividerHeight(height: Float) { + sentenceDividerHeight = height + if (hasLrc()) { + smoothScrollTo(currentLine) + } + postInvalidate() + } + + override fun setTranslateDividerHeight(height: Float) { + translateDividerHeight = height + if (hasLrc()) { + smoothScrollTo(currentLine) + } + postInvalidate() + } + + override fun setHorizontalOffset(offset: Float) { + horizontalOffset = offset + initPlayDrawable() + postInvalidate() + } + + override fun setHorizontalOffsetPercent(percent: Float) { + horizontalOffsetPercent = percent + initPlayDrawable() + postInvalidate() + } + + override fun setTranslateTextScaleValue(scaleValue: Float) { + translateTextScaleValue = scaleValue + initEntryList() + if (hasLrc()) { + smoothScrollTo(currentLine) + } + } + + override fun setTextGravity(gravity: Int) { + textGravity = gravity + initEntryList() + if (hasLrc()) { + smoothScrollTo(currentLine) + } + } + + override fun setNormalColor(normalColor: Int) { + normalTextColor = normalColor + postInvalidate() + } + + override fun setNormalTextSize(size: Float) { + normalTextSize = size + initEntryList() + if (hasLrc()) { + smoothScrollTo(currentLine) + } + } + + override fun setCurrentTextSize(size: Float) { + currentTextSize = size + initEntryList() + if (hasLrc()) { + smoothScrollTo(currentLine) + } + } + + override fun setCurrentColor(currentColor: Int) { + currentTextColor = currentColor + postInvalidate() + } + + override fun setTimelineTextColor(timelineTextColor: Int) { + this.timelineTextColor = timelineTextColor + postInvalidate() + } + + override fun setTimelineColor(timelineColor: Int) { + this.timelineColor = timelineColor + postInvalidate() + } + + override fun setTimeTextColor(timeTextColor: Int) { + this.timeTextColor = timeTextColor + postInvalidate() + } + + override fun setLabel(label: String) { + runOnMain { + defaultLabel = label + this@LyricViewX.invalidate() + } + } + + override fun loadLyric(mainLyricText: String?, secondLyricText: String?) { + runOnMain { + reset() + val sb = StringBuilder("file://") + sb.append(mainLyricText) + if (secondLyricText != null) { + sb.append("#").append(secondLyricText) + } + val flag = sb.toString() + this@LyricViewX.flag = flag + thread { + val lrcEntries = LyricUtil.parseLrc(arrayOf(mainLyricText, secondLyricText)) + runOnMain { + onLrcLoaded(lrcEntries) + this@LyricViewX.flag = null + } + } + } + } + + override fun loadLyric(lyricEntries: List) { + runOnMain { + reset() + onLrcLoaded(lyricEntries) + } + } + + override fun updateTime(time: Long, force: Boolean) { + // 将方法的执行延后至 View 创建完成后执行 + readyHelper.whenReady { + if (!it) return@whenReady + if (hasLrc()) { + val line = findShowLine(time) + if (line != currentLine) { + runOnMain { + currentLine = line + smoothScrollTo(line) + } + } + } + } + } + + override fun setDraggable(draggable: Boolean, onPlayClickListener: OnPlayClickListener?) { + this.onPlayClickListener = if (draggable) { + requireNotNull(onPlayClickListener) { + "if draggable == true, onPlayClickListener must not be null" } + onPlayClickListener + } else { + null + } + } + + override fun setOnSingerClickListener(onSingerClickListener: OnSingleClickListener?) { + this.onSingerClickListener = onSingerClickListener + } + + override fun getLyricEntryList(): List { + return lyricEntryList.toList() + } + + override fun setLyricEntryList(newList: List) { + reset() + onLrcLoaded(newList) + this@LyricViewX.flag = null + } + + override fun getCurrentLineLyricEntry(): LyricEntry? { + if (currentLine <= lyricEntryList.lastIndex) { + return lyricEntryList[currentLine] + } + return null + } + + override fun setLyricTypeface(file: File) { + val typeface = file.takeIf { it.exists() } + ?.runCatching { Typeface.createFromFile(this) } + ?.getOrNull() ?: return + + setLyricTypeface(typeface) + } + + override fun setLyricTypeface(path: String) { + setLyricTypeface(File(path)) + } + + override fun setLyricTypeface(typeface: Typeface?) { + lyricPaint.typeface = typeface + secondLyricPaint.typeface = typeface + postInvalidate() + } + + override fun setDampingRatioForLyric(dampingRatio: Float) { + dampingRatioForLyric = dampingRatio + progressSpringAnimator.spring.dampingRatio = dampingRatio + } + + override fun setDampingRatioForViewPort(dampingRatio: Float) { + dampingRatioForViewPort = dampingRatio + viewPortSpringAnimator.spring.dampingRatio = dampingRatio + } + + override fun setStiffnessForLyric(stiffness: Float) { + stiffnessForLyric = stiffness + progressSpringAnimator.spring.stiffness = stiffness + } + + override fun setStiffnessForViewPort(stiffness: Float) { + stiffnessForViewPort = stiffness + viewPortSpringAnimator.spring.stiffness = stiffness + } + + override fun setPlayDrawable(drawable: Drawable) { + playDrawable = drawable + } + + override fun setIsDrawTranslation(isDrawTranslation: Boolean) { + this.isDrawTranslation = isDrawTranslation + postInvalidate() + } + + override fun setIsEnableBlurEffect(isEnableBlurEffect: Boolean) { + this.isEnableBlurEffect.value = isEnableBlurEffect + postInvalidate() + } + + override fun setItemOffsetPercent(itemOffsetPercent: Float) { + this.itemOffsetPercent = itemOffsetPercent + postInvalidate() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/dirror/lyricviewx/ReadyHelper.kt b/app/src/main/java/com/dirror/lyricviewx/ReadyHelper.kt new file mode 100644 index 0000000..9b81331 --- /dev/null +++ b/app/src/main/java/com/dirror/lyricviewx/ReadyHelper.kt @@ -0,0 +1,51 @@ +package com.dirror.lyricviewx + +import androidx.annotation.IntDef + +const val STATE_CREATED = 1 +const val STATE_INITIALIZING = 2 +const val STATE_INITIALIZED = 3 +const val STATE_ERROR = 4 + +@IntDef( + STATE_CREATED, + STATE_INITIALIZING, + STATE_INITIALIZED, + STATE_ERROR +) +@Retention(AnnotationRetention.SOURCE) +annotation class ReadyState + +/** + * 简单的状态机,根据 [readyState] 的状态决定当前任务的执行或延后与否 + */ +open class ReadyHelper { + private var readyCallback: (Boolean) -> Unit = {} + + @ReadyState + var readyState: Int = STATE_CREATED + set(value) { + if (field == value) return + when (value) { + STATE_INITIALIZED, + STATE_ERROR -> synchronized(readyCallback) { + field = value + readyCallback.invoke(value != STATE_ERROR) + } + else -> field = value + } + } + + fun whenReady(performAction: (Boolean) -> Unit): Boolean { + return when (readyState) { + STATE_CREATED, STATE_INITIALIZING -> { + readyCallback = performAction + false + } + else -> { + performAction(readyState != STATE_ERROR) + true + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dirror/lyricviewx/extension/BlurMaskFilterExt.kt b/app/src/main/java/com/dirror/lyricviewx/extension/BlurMaskFilterExt.kt new file mode 100644 index 0000000..f5a8558 --- /dev/null +++ b/app/src/main/java/com/dirror/lyricviewx/extension/BlurMaskFilterExt.kt @@ -0,0 +1,15 @@ +package com.dirror.lyricviewx.extension + +import android.graphics.BlurMaskFilter +import android.util.SparseArray + +class BlurMaskFilterExt { + private val maskFilterCache = SparseArray() + + fun get(radius: Int): BlurMaskFilter? { + if (radius == 0 || radius > 25) return null + + return maskFilterCache[radius] ?: BlurMaskFilter(radius.toFloat(), BlurMaskFilter.Blur.NORMAL) + .also { maskFilterCache.put(radius, it) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/MP3.java b/app/src/main/java/com/muqingbfq/MP3.java new file mode 100644 index 0000000..00e9651 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/MP3.java @@ -0,0 +1,50 @@ +package com.muqingbfq; + +import androidx.annotation.NonNull; + +import java.io.Serializable; +import java.util.Objects; + +public class MP3 implements Serializable { + public String id, name, zz, url; + // 音乐的贴图 + public String picurl; + + + public MP3(String id, String name, String zz, String picurl) { + this.id = id; + this.name = name; + this.zz = zz; + this.picurl = picurl; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MP3)) return false; + MP3 mp3 = (MP3) o; + if (id.equals(mp3.id)) { + return true; + } + return Objects.equals(id, mp3.id) && + Objects.equals(name, mp3.name) && + Objects.equals(zz, mp3.zz) && + Objects.equals(picurl, mp3.picurl); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, zz, picurl); + } + + @NonNull + @Override + public String toString() { + return "MP3{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", zz='" + zz + '\'' + + ", picurl=" + picurl + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/MediaPlayer.java b/app/src/main/java/com/muqingbfq/MediaPlayer.java new file mode 100644 index 0000000..00382cb --- /dev/null +++ b/app/src/main/java/com/muqingbfq/MediaPlayer.java @@ -0,0 +1,170 @@ +package com.muqingbfq; + +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.AudioAttributes; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; +import com.mpatric.mp3agic.ID3v2; +import com.mpatric.mp3agic.Mp3File; +import com.muqingbfq.fragment.bflb_db; +import com.muqingbfq.fragment.mp3; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; + +import java.io.IOException; + +public class MediaPlayer extends android.media.MediaPlayer { + // 每秒更新一次进度 + @SuppressLint("UnsafeOptInUsageError") + public MediaPlayer() { + setOnErrorListener((mediaPlayer, i, i1) -> { + if (bfqkz.list.isEmpty()) { + return false; + } + //针对错误进行相应的处理 + bfqkz.list.remove(bfqkz.xm); + bfqkz.xm = bfqkz.list.get(bfqkz.getmti()); + new bfqkz.mp3(com.muqingbfq.api. + url.hq(bfqkz.xm)); + return false; + }); + setOnCompletionListener(mediaPlayer -> { + if (bfqkz.list.isEmpty()) { + return; + } + bfq_an.xyq(); + }); + setAudioAttributes(new AudioAttributes + .Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build()); +// main.handler.post(updateSeekBar); // 在播放开始时启动更新进度 + } + + @Override + public void pause() throws IllegalStateException { + if (isPlaying()) { + super.pause(); +// bfq.isPlaying = false; + } + + if (bfqkz.notify != null) { + bfqkz.notify.tzl_button(); + } + } + + @Override + public void start() throws IllegalStateException { + if (bfqkz.xm == null) { + if (bfqkz.list != null && !bfqkz.list.isEmpty()) { + bfq_an.xyq(); + } + return; + } + super.start(); + + if (bfqkz.notify != null) { + bfqkz.notify.tzl_button(); + } +// bfq.isPlaying = true; + } + + + public void setDataSource(MP3 mp3) throws IOException { + reset(); + super.setDataSource(mp3.url); + prepare(); + start(); + bfqkz.xm = mp3; + main.handler.post(() -> { + bfui(); + if (bfqkz.notify != null) { + bfqkz.notify.tzl(); + } + }); + + new Thread() { + @Override + public void run() { + super.run(); + if (bfqkz.lishi_list.size() >= 100) { + bfqkz.lishi_list.remove(0); + } + bfqkz.lishi_list.remove(bfqkz.xm); + if (!bfqkz.lishi_list.contains(bfqkz.xm)) { + bfqkz.lishi_list.add(0, bfqkz.xm); + wj.xrwb(wj.gd + "mp3_hc.json", new com.google.gson.Gson().toJson(bfqkz.lishi_list)); + } + wj.setMP3ToFile(bfqkz.xm); + } + }.start(); + } + + public void DataSource(MP3 path) throws Exception { + reset(); + super.setDataSource(path.url); + prepare(); + setTX(); + } + + public void setTX() { + Glide.with(main.application) + .asBitmap() + .load(bfqkz.xm.picurl) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, + @NonNull Target target, + boolean isFirstResource) { + Bitmap bitmap = null; + try { + Mp3File mp3file = new Mp3File(bfqkz.xm.picurl); + if (mp3file.hasId3v2Tag()) { + ID3v2 id3v2Tag = mp3file.getId3v2Tag(); + byte[] albumImage = id3v2Tag.getAlbumImage(); + bitmap = + BitmapFactory.decodeByteArray(albumImage, 0, albumImage.length); + + } + } catch (Exception a) { + gj.sc(getClass() + " yc:" + a); + } + if (bfqkz.notify != null) { + bfqkz.notify.setbitmap(bitmap); + } + return false; + } + + @Override + public boolean onResourceReady(@NonNull Bitmap bitmap, @NonNull Object model, Target target, + @NonNull DataSource dataSource, + boolean isFirstResource) { + if (bfqkz.notify != null) { + bfqkz.notify.setbitmap(bitmap); + } + return false; + } + }) + .submit(); + } + @SuppressLint("NotifyDataSetChanged") + public void bfui() { + setTX(); + if (bflb_db.adapter != null) { +// bflb_db.adapter. + bflb_db.adapter.notifyDataSetChanged(); + } + if (mp3.adapter != null) { + mp3.adapter.notifyDataSetChanged(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/MyButtonClickReceiver.java b/app/src/main/java/com/muqingbfq/MyButtonClickReceiver.java new file mode 100644 index 0000000..3b2feba --- /dev/null +++ b/app/src/main/java/com/muqingbfq/MyButtonClickReceiver.java @@ -0,0 +1,213 @@ +package com.muqingbfq; + +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; +import android.view.KeyEvent; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.muqingbfq.mq.FloatingLyricsService; +import com.muqingbfq.mq.wj; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +public class MyButtonClickReceiver extends BroadcastReceiver { + private final Timer timer = new Timer(); + private static int clickCount; + + public MyButtonClickReceiver() { + super(); + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { + int bluetoothState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 0); + //蓝牙断开 + if (bluetoothState == BluetoothAdapter.STATE_DISCONNECTED) { + receiverPause(); + } + return; + } + if (action.equals("android.intent.action.HEADSET_PLUG")) { + if (intent.hasExtra("state")) { + if (intent.getIntExtra("state", 2) == 0) { + //拔出 + if (bfqkz.mt.isPlaying()) { + receiverPause(); + } + } else if (intent.getIntExtra("state", 2) == 1) { + receiverPlay(); + //插入 + } + } + return; + } + if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) { + KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); + if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK && keyEvent.getAction() == KeyEvent.ACTION_UP) { + clickCount = clickCount + 1; + if (clickCount == 1) { + HeadsetTimerTask headsetTimerTask = new HeadsetTimerTask(); + timer.schedule(headsetTimerTask, 500); + } + } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_NEXT && keyEvent.getAction() == KeyEvent.ACTION_UP) { + handler(2); + } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PREVIOUS && keyEvent.getAction() == KeyEvent.ACTION_UP) { + handler(3); + } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE && keyEvent.getAction() == KeyEvent.ACTION_UP) { + handler(4); + } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY && keyEvent.getAction() == KeyEvent.ACTION_UP) { + handler(5); + } + return; + } + + switch (action) { + case "kg": + playOrPause(); + break; + case "syq": + bfq_an.syq(); + break; + case "xyq": + bfq_an.xyq(); + break; + case "lrc": + if (FloatingLyricsService.lei == null) { + if (!Settings.canDrawOverlays(main.application)) { + // 无权限,需要申请权限 + main.application.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + main.application.getPackageName()))); + } else { + main.application.startService( + new Intent(main.application, FloatingLyricsService.class)); + } + return; + } + FloatingLyricsService lei = FloatingLyricsService.lei; + if (lei.setup.i == 1) { + lei.setyc(); + } else { + lei.setup.i = 0; + lei.baocun(); + main.application.stopService( + new Intent(main.application, FloatingLyricsService.class)); + } + break; + case "like": + try { + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + List list = gson.fromJson(wj.dqwb(wj.gd + "mp3_like.json"), type); + if (list == null) { + list = new ArrayList<>(); + } + if (bfqkz.like_bool) { + list.remove(bfqkz.xm); + bfqkz.like_bool = false; + } else { + if (!list.contains(bfqkz.xm)) { + list.add(bfqkz.xm); + bfqkz.like_bool = true; + } + } + wj.xrwb(wj.gd + "mp3_like.json", gson.toJson(list)); + } catch (Exception e) { + e.printStackTrace(); + } + break; + } + // 处理按钮点击事件的逻辑 + } + + class HeadsetTimerTask extends TimerTask { + @Override + public void run() { + try { + if (clickCount == 1) { + handler(1); + } else if (clickCount == 2) { + handler(2); + } else if (clickCount >= 3) { + handler(3); + } + clickCount = 0; + } catch (Exception e) { + e.printStackTrace(); + } + } + } + private void handler(int a) { + switch (a) { + case 1: + playOrPause(); + break; + case 2: + playNext(); + break; + case 3: + playPrevious(); + break; + case 4: + receiverPause(); + break; + case 5: + receiverPlay(); + break; + default: + break; + } + } + + + // * 对蓝牙 播放 + public void receiverPlay() { + bfqkz.mt.start(); + } + + // * 对蓝牙 暂停 + public void receiverPause() { + bfqkz.mt.pause(); + } + + /** + * 对蓝牙 播放-暂停 + */ + public static void playOrPause() { + if (bfqkz.mt == null) { + return; + } +// gj.sc(isMusicServiceBound);播放/暂停按钮点击事件 if (isMusicServiceBound) + if (bfqkz.mt.isPlaying()) { + bfqkz.mt.pause(); + } else { + bfqkz.mt.start(); + } + bfqkz.notify.tzl(); + } + + /** + * 对蓝牙 下一首 + */ + public void playNext() { + bfq_an.xyq(); + } + + /** + * 对蓝牙 上一首 + */ + public void playPrevious() { + bfq_an.syq(); + } +} diff --git a/app/src/main/java/com/muqingbfq/XM.java b/app/src/main/java/com/muqingbfq/XM.java new file mode 100644 index 0000000..5f44994 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/XM.java @@ -0,0 +1,22 @@ +package com.muqingbfq; + +public class XM { + public String id, name, message; + public Object picurl; + public XM(String id, String name, String picurl) { + this.id = id; + this.name = name; + this.picurl = picurl; + } + public XM(String id, String name,String message, String picurl) { + this.id = id; + this.name = name; + this.picurl = picurl; + this.message = message; + } + public XM(String id, String name, int picurl) { + this.id = id; + this.name = name; + this.picurl = picurl; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/activity_about_software.java b/app/src/main/java/com/muqingbfq/activity_about_software.java new file mode 100644 index 0000000..3324292 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/activity_about_software.java @@ -0,0 +1,185 @@ +package com.muqingbfq; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.os.Build; +import android.os.Bundle; +import android.text.Html; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +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.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDragHandleView; +import com.muqingbfq.databinding.ActivityAboutSoftwareBinding; +import com.muqingbfq.databinding.ListKaifazheBinding; +import com.muqingbfq.mq.AppCompatActivity; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +public class activity_about_software extends AppCompatActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(); + try { + String versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + binding.text2.setText(String.format("%s Bate", versionName)); + } catch (PackageManager.NameNotFoundException e) { + yc.start(this, e); + } + setSupportActionBar(findViewById(R.id.toolbar)); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + findViewById(R.id.button1).setOnClickListener(view -> { + new Thread() { + @Override + public void run() { + super.run(); + int jianchagengxin = gj.jianchagengxin(activity_about_software.this); + if (jianchagengxin == 400) { + gj.xcts(activity_about_software.this, "无网络"); + } else if (jianchagengxin == 0) { + gj.xcts(activity_about_software.this, "已经是最新的客户端了"); + } + } + }.start(); + }); + TextView viewById = findViewById(R.id.text1); + AssetManager assets = getAssets(); + try { + InputStream open = assets.open("about.html"); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(open)); + StringBuilder stringBuilder = new StringBuilder(); + String ling; + while ((ling = bufferedReader.readLine()) != null) { + stringBuilder.append(ling); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + viewById.setText(Html.fromHtml(stringBuilder.toString(), Html.FROM_HTML_MODE_LEGACY)); + } else { + viewById.setText(Html.fromHtml(stringBuilder.toString())); + } + open.close(); + bufferedReader.close(); + + } catch (IOException e) { + e.printStackTrace(); + viewById.setText(String.format("错误:%s", e)); + } + } + + MenuItem itemA; + @Override + public boolean onCreateOptionsMenu(Menu menu) { + itemA= menu.add("特别鸣谢"); + itemA.setTitle("特别鸣谢"); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + finish(); + } else if (item == itemA) { + new botton(this); + } + return super.onOptionsItemSelected(item); + } + + @Override + protected ActivityAboutSoftwareBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityAboutSoftwareBinding.inflate(layoutInflater); + } + + class botton extends BottomSheetDialog { + + List list = new ArrayList<>(); + public botton(@NonNull Context context) { + super(context); + setTitle("特别鸣谢"); + list.add(new Object[]{"2923268971","薄荷今天吃什么?", "维护开发者", "QQ"}); + list.add(new Object[]{"3301074923","威廉", "主要测试BUG", "QQ"}); + show(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + LinearLayout linearLayout = new LinearLayout(getContext()); + RecyclerView recyclerView = new RecyclerView(getContext()); + recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER); + recyclerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + + linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setPadding(50,0,50,500); + + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + recyclerView.setAdapter(new RecyclerView.Adapter() { + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + ListKaifazheBinding binding = ListKaifazheBinding.inflate(getLayoutInflater()); + return new VH(binding.getRoot()); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + Object[] objects = list.get(position); + holder.name.setText(objects[1].toString()); + holder.zz.setText(objects[2].toString()); +// https://q1.qlogo.cn/g?b=qq&nk=1966944300&s=100 + Glide.with(getContext()) + .load("https://q1.qlogo.cn/g?b=qq&nk=" + objects[0] + "&s=100") + .error(R.drawable.ic_launcher_foreground) + .into(holder.imageView); + } + + @Override + public int getItemCount() { + return list.size(); + } + }); + linearLayout.setGravity(Gravity.CENTER_HORIZONTAL); + linearLayout.addView(new BottomSheetDragHandleView(getContext())); + linearLayout.addView(recyclerView); + setContentView(linearLayout); + } + } + + class VH extends RecyclerView.ViewHolder { + public TextView name, zz; + public ImageView imageView; + public VH(@NonNull View itemView) { + super(itemView); + name = itemView.findViewById(R.id.text1); + zz = itemView.findViewById(R.id.text2); + imageView = itemView.findViewById(R.id.imageView); + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/activity_search.java b/app/src/main/java/com/muqingbfq/activity_search.java new file mode 100644 index 0000000..b21d88f --- /dev/null +++ b/app/src/main/java/com/muqingbfq/activity_search.java @@ -0,0 +1,352 @@ +package com.muqingbfq; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityOptions; +import android.content.Intent; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.google.android.flexbox.AlignItems; +import com.google.android.flexbox.FlexDirection; +import com.google.android.flexbox.FlexWrap; +import com.google.android.flexbox.FlexboxLayoutManager; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.search.SearchView; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.muqingbfq.databinding.ActivitySearchBinding; +import com.muqingbfq.databinding.ListTextBinding; +import com.muqingbfq.fragment.search; +import com.muqingbfq.mq.FragmentActivity; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; +import com.muqingbfq.view.Edit; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class activity_search extends FragmentActivity { + private List json_list = new ArrayList<>(); + private final List list = new ArrayList<>(); + + public static void start(Activity context, View view) { + ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(context, + view, "edit"); + context.startActivity(new Intent(context, activity_search.class), options.toBundle()); + } + + class VH extends RecyclerView.ViewHolder { + public VH(@NonNull View itemView) { + super(itemView); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(getViewBinding().getRoot()); + setToolbar(); + FlexboxLayoutManager manager = new FlexboxLayoutManager(this) { + @Override + public boolean canScrollVertically() { + return false; + } + }; + //设置主轴排列方式 + manager.setFlexDirection(FlexDirection.ROW); + //设置是否换行 + manager.setFlexWrap(FlexWrap.WRAP); + manager.setAlignItems(AlignItems.STRETCH);//历史记录的LayoutManager + binding.listRecycler.setLayoutManager(manager); + binding.listRecycler.setAdapter(new SearchRecordAdapter()); + binding.deleat.setOnClickListener(v -> new MaterialAlertDialogBuilder( + activity_search.this) + .setTitle("删除") + .setMessage("清空历史记录?") + .setNegativeButton("取消", null) + .setPositiveButton("确定", (dialogInterface, ii) -> { + int i = 0; + Iterator iterator = json_list.iterator(); + while (iterator.hasNext()) { + iterator.next(); + iterator.remove(); + binding.listRecycler.getAdapter().notifyItemRemoved(i++); + } + binding.xxbj1.setVisibility(View.GONE); + wj.sc(wj.filesdri + wj.lishi_json); + }) + .show()); + binding.searchRecycler.setLayoutManager(new LinearLayoutManager(this)); + binding.searchRecycler.setAdapter(new RecyclerView.Adapter() { + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.view_search_item, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + String s = list.get(position); + ((TextView) holder.itemView).setText(s); + holder.itemView.setOnClickListener(v -> { + binding.searchview.setText(s); + start(s); + }); + } + + @Override + public int getItemCount() { + return list.size(); + } + }); + //设置项点击监听 + final Object o = new Object(); + binding.searchview.getEditText().addTextChangedListener(new Edit.TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + new Thread() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + synchronized (o) { + list.clear(); + String hq = com.muqingbfq.mq.wl. + hq("/search/suggest?keywords=" + s + "&type=mobile"); + try { + JSONArray jsonArray = new JSONObject(hq).getJSONObject("result") + .getJSONArray("allMatch"); + int length = jsonArray.length(); + for (int i = 0; i < length; i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String keyword = jsonObject.getString("keyword"); + list.add(keyword); + } + activity_search.this.runOnUiThread(() -> + binding.searchRecycler.getAdapter().notifyDataSetChanged()); + } catch (Exception e) { + gj.sc(e); + } + } + } + }.start(); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); +/* if (binding.toolbar.collapse(contextualToolbar, binding.barlayout)) { + // Clear selection. + return; + }*/ + binding.searchview.inflateMenu(R.menu.search); + binding.searchview.setOnMenuItemClickListener( + menuItem -> { + binding.searchview.hide(); + start(binding.searchview.getText().toString()); + return true; + }); + + binding.searchview.addTransitionListener( + (searchView, previousState, newState) -> { + if (newState == SearchView.TransitionState.SHOWING || + newState == SearchView.TransitionState.SHOWN) { + searchView.setText(binding.toolbar.getText()); + } else if (newState == SearchView.TransitionState.HIDING || + newState == SearchView.TransitionState.HIDDEN) { + binding.toolbar.setText(searchView.getText()); + } + }); + binding.searchview + .getEditText() + .setOnEditorActionListener( + (v, actionId, event) -> { + binding.toolbar.setText(binding.searchview.getText()); + binding.searchview.hide(); + start(binding.searchview.getText().toString()); + return false; + }); + binding.toolbar.setOnMenuItemClickListener(item -> { + //搜索 + start(binding.toolbar.getText().toString()); + return true; + }); + } + + public void dismiss() { + binding.searchview.hide(); + } + + private void addSearchRecord(String name) { + try { + if (!binding.xxbj1.isShown()) { + binding.xxbj1.setVisibility(View.VISIBLE); + } + int existingIndex = json_list.indexOf(name); + if (existingIndex != -1) { + // 交换两个元素的位置 + json_list.remove(name); + json_list.add(0, name); + binding.listRecycler.getAdapter().notifyItemMoved(existingIndex, 0); + } else { +// json_list.remove(name); + json_list.add(0, name); + binding.listRecycler.getAdapter().notifyItemInserted(0); + } + wj.xrwb(wj.filesdri + wj.lishi_json, new Gson().toJson(json_list)); + } catch (Exception e) { + gj.sc(e); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuItem sousuo = menu.add("搜索"); + sousuo.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + return super.onCreateOptionsMenu(menu); + } + + @Override + protected ActivitySearchBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivitySearchBinding.inflate(layoutInflater); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + end(); + } + return true; + } + + public void start(String name) { + dismiss(); + if (!TextUtils.isEmpty(name)) { + search sea = (search) getSupportFragmentManager().findFragmentById(R.id.search_fragment); + binding.searchFragment.setVisibility(View.VISIBLE); + sea.sx(name); + addSearchRecord(name); + } + } + + class SearchRecordAdapter extends RecyclerView.Adapter { + public SearchRecordAdapter() { + String dqwb = wj.dqwb(wj.filesdri + wj.lishi_json); + if (dqwb != null) { + try { + json_list = new Gson().fromJson(dqwb, new TypeToken>() { + }.getType()); + } catch (Exception e) { + wj.sc(wj.filesdri + wj.lishi_json); + yc.start(activity_search.this, e); + } + } + if (json_list.isEmpty()) { + binding.xxbj1.setVisibility(View.INVISIBLE); + } + RecyclerView.ItemAnimator animator = new DefaultItemAnimator() { + @Override + public boolean animateRemove(RecyclerView.ViewHolder holder) { + ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(holder.itemView, "alpha", 1f, 0f); + fadeAnimator.setDuration(getRemoveDuration()); + fadeAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + dispatchRemoveFinished(holder); + holder.itemView.setAlpha(1f); + } + }); + fadeAnimator.start(); + return false; + } + }; + binding.listRecycler.setItemAnimator(animator); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(ListTextBinding.inflate(LayoutInflater.from(parent.getContext()), + parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + String keyword = json_list.get(position); + holder.binding.getRoot().setText(keyword); + holder.binding.getRoot().setOnClickListener(v -> { + binding.toolbar.setText(keyword); + start(keyword); + }); + holder.binding.getRoot().setOnCloseIconClickListener(view -> { + json_list.remove(keyword); + notifyItemRemoved(holder.getAdapterPosition()); + wj.xrwb(wj.filesdri + wj.lishi_json, new Gson().toJson(json_list)); + }); + } + + @Override + public int getItemCount() { + return json_list.size(); + } + + class ViewHolder extends RecyclerView.ViewHolder { + ListTextBinding binding; + + + public ViewHolder(ListTextBinding itemView) { + super(itemView.getRoot()); + binding = itemView; + } + } + } + + @Override + public void onBackPressed() { + end(); + } + + private void end() { + if (binding.searchview.isShowing()) { + binding.searchview.hide(); + return; + } + if (binding.searchFragment.getVisibility() == View.VISIBLE) { + binding.searchFragment.setVisibility(View.GONE); + } else { + finish(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); +// ActivityCompat.finishAffinity(this); + } + } + + @Override + public void finish() { + super.finish(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/adapter/AdapterMp3.java b/app/src/main/java/com/muqingbfq/adapter/AdapterMp3.java new file mode 100644 index 0000000..2283f05 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/adapter/AdapterMp3.java @@ -0,0 +1,76 @@ +package com.muqingbfq.adapter; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.muqingbfq.MP3; +import com.muqingbfq.R; +import com.muqingbfq.api.url; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.ListMp3ImageBinding; +import com.muqingbfq.mq.VH; +import com.muqingbfq.mq.gj; + +import java.util.ArrayList; +import java.util.List; + +public class AdapterMp3 extends RecyclerView.Adapter> { + public List list = new ArrayList<>(); + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH<>(ListMp3ImageBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); + } + + int position_wei; + @SuppressLint("NotifyDataSetChanged") + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + MP3 x = list.get(position); + holder.binding.wb1.setText(x.name); + holder.binding.zz.setText(x.zz); + if (bfqkz.xm != null && x.id.equals(bfqkz.xm.id)) { + holder.binding.getRoot().setCardBackgroundColor( + gj.getThemeColor(holder.itemView.getContext(), com.google.android.material.R.attr.colorSurfaceVariant)); + } else { + holder.binding.getRoot().setCardBackgroundColor(ContextCompat + .getColor(holder.itemView.getContext(), android.R.color.transparent)); + } + holder.itemView.setOnClickListener(view -> { + if (bfqkz.xm == null || !bfqkz.xm.id.equals(x.id)) { + bfqkz.xm = x; + new url(x); + notifyDataSetChanged(); + + } else if (!bfqkz.mt.isPlaying()) { + bfqkz.mt.start(); + } + if (!bfqkz.list.contains(x)) { + bfqkz.list.add(0, x); + } +// bfqkz.list.addAll(list); +// bfq.start(getContext()); + }); + Glide.with(holder.itemView.getContext()).load(x.picurl) + .apply(new RequestOptions().placeholder(R.drawable.ic_launcher_foreground)) + .error(R.drawable.ic_launcher_foreground) + .into(holder.binding.imageView); + } + + @Override + public int getItemCount() { + return list.size(); + } + +} diff --git a/app/src/main/java/com/muqingbfq/api/FileDownloader.java b/app/src/main/java/com/muqingbfq/api/FileDownloader.java new file mode 100644 index 0000000..d578a3f --- /dev/null +++ b/app/src/main/java/com/muqingbfq/api/FileDownloader.java @@ -0,0 +1,181 @@ +package com.muqingbfq.api; + + +import android.annotation.SuppressLint; +import android.content.Context; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.mpatric.mp3agic.ID3v2; +import com.mpatric.mp3agic.Mp3File; +import com.muqingbfq.MP3; +import com.muqingbfq.main; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; +import com.muqingbfq.mq.wl; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class FileDownloader { + OkHttpClient client = new OkHttpClient(); + AlertDialog dialog; + TextView textView; + Context context; + public FileDownloader(Context context) { + this.context = context; + main.handler.post(() -> { + textView = new TextView(context); + dialog = new MaterialAlertDialogBuilder(context) + .setTitle("下载中...") + .setView(textView) + .show(); + }); + } + public void downloadFile(MP3 x) { + Request request = new Request.Builder() + .url(main.api + url.api + "?id=" + x.id + "&level=" + + "standard" + "&cookie=" + wl.Cookie) + .build(); + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + e.printStackTrace(); + // 下载失败处理 + } + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (!response.isSuccessful()) { + // 下载失败处理 + return; + } + try { + JSONObject json = new JSONObject(response.body().string()); + JSONArray data = json.getJSONArray("data"); + JSONObject jsonObject = data.getJSONObject(0); + String url = jsonObject.getString("url"); + downloadFile(url, x); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + long fileSizeDownloaded = 0; + public void downloadFile(String url, MP3 x) { + Request request = new Request.Builder() + .url(url) + .build(); + // 创建通知渠道(仅适用于Android 8.0及以上版本) + // 发起请求 + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + e.printStackTrace(); + // 下载失败处理 + } + + @SuppressLint("SetTextI18n") + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + if (!response.isSuccessful()) { + // 下载失败处理 + return; + } + File outputFile = new File(wj.mp3, x.id + ".mp3"); + File parentFile = outputFile.getParentFile(); + if (!parentFile.isDirectory()) { + parentFile.mkdirs(); + } + InputStream inputStream = null; + FileOutputStream outputStream = null; + try { + byte[] buffer = new byte[4096]; + long fileSize = response.body().contentLength(); + inputStream = response.body().byteStream(); + outputStream = new FileOutputStream(outputFile); + + int read; + fileSizeDownloaded = 0; + while ((read = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, read); + fileSizeDownloaded += read; + // 更新通知栏进度 +// updateNotificationProgress(context, fileSize, fileSizeDownloaded); + if (textView != null) { + main.handler.post(() -> + textView.setText(x.name + ":" + + (int) ((fileSizeDownloaded * 100) / fileSize))); + } + } + try { + Mp3File mp3file = new Mp3File(outputFile); + if (mp3file.hasId3v2Tag()) { + ID3v2 id3v2Tag = mp3file.getId3v2Tag(); + // 设置新的ID值 + gj.sc(x.name); + id3v2Tag.setTitle(x.name); + id3v2Tag.setArtist(x.zz); + id3v2Tag.setAlbum(x.zz); + id3v2Tag.setLyrics(com.muqingbfq.api.url.Lrc(x.id)); + ByteArrayOutputStream o = new ByteArrayOutputStream(); + if (x.picurl instanceof String) { + Request build = new Request.Builder().url(x.picurl) + .build(); + Response execute = client.newCall(build).execute(); + if (execute.isSuccessful()) { + id3v2Tag.setAlbumImage(execute.body().bytes() + , "image/jpeg"); + } + } + o.close(); + mp3file.save(wj.mp3 + x.id); + outputFile.delete(); + } + // 保存修改后的音乐文件,删除原来的文件 + } catch (Exception e) { + gj.sc(e); + outputFile.delete(); + } + dismiss(); + // 下载完成处理 + } catch (IOException e) { + e.printStackTrace(); + // 下载失败处理 + } finally { + if (inputStream != null) { + inputStream.close(); + } + if (outputStream != null) { + outputStream.close(); + } + dismiss(); + } + } + }); + } + + public void dismiss() { + if (dialog == null) { + return; + } + + main.handler.post(() -> dialog.dismiss()); + } +} diff --git a/app/src/main/java/com/muqingbfq/api/playlist.java b/app/src/main/java/com/muqingbfq/api/playlist.java new file mode 100644 index 0000000..568452c --- /dev/null +++ b/app/src/main/java/com/muqingbfq/api/playlist.java @@ -0,0 +1,182 @@ +package com.muqingbfq.api; + +import android.app.Activity; +import android.os.Environment; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.mpatric.mp3agic.ID3v2; +import com.mpatric.mp3agic.Mp3File; +import com.muqingbfq.MP3; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; +import com.muqingbfq.mq.wl; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.List; + +public class playlist extends Thread { + public static final String api = "/playlist/track/all?id="; + + public static String gethq(String uid) { + if (wj.cz(wj.filesdri + "user.mq")) { + return wl.hq(api + uid + "&limit=100" + "&cookie=" + wl.Cookie); +// gj.sc(hq); + } else { + return wl.hq(api + uid + "&limit=100"); + } + } + + public static boolean hq(List list, String uid) { + switch (uid) { + case "mp3_xz.json": + return playlist.hq_xz(list); + case "mp3_like.json": + return playlist.hq_like(list); + case "mp3_hc.json": + return hq_hc(list); + } + list.clear(); + try { + String hq = wj.dqwb(wj.gd + uid); + if (hq == null || hq.isEmpty()) { + hq = gethq(uid); + } + list.clear(); + JSONObject json = new JSONObject(hq); + JSONArray songs = json.getJSONArray("songs"); + int length = songs.length(); + for (int i = 0; i < length; i++) { + JSONObject jsonObject = songs.getJSONObject(i); + String id = jsonObject.getString("id"); + String name = jsonObject.getString("name"); + try { + String tns = jsonObject.getString("tns"); + tns = tns.replace("[\"", "("); + tns = tns.replace("\"]", ")"); + name += tns; + } catch (Exception e) { + e.printStackTrace(); + } + JSONObject al = jsonObject.getJSONObject("al"); + JSONArray ar = jsonObject.getJSONArray("ar"); + StringBuilder zz = new StringBuilder(); + int length_a = ar.length(); + for (int j = 0; j < length_a; j++) { + zz.append(ar.getJSONObject(j).getString("name")) + .append("/"); + } + zz.append("-").append(al.getString("name")); + String picUrl = al.getString("picUrl"); + list.add(new MP3(id, name, zz.toString(), picUrl)); + } + return true; + } catch (Exception e) { + gj.sc("失败的错误 " + e); + } + return false; + } + + public static boolean hq_like(List list) { + try { + String dqwb = wj.dqwb(wj.gd + "mp3_like.json"); + if (dqwb == null) { + return false; + } + Type type = new TypeToken>() { + }.getType(); + Gson gson = new Gson(); + list.clear(); + list.addAll(gson.fromJson(dqwb, type)); + return true; + } catch (Exception e) { + gj.sc("失败的错误 " + e); + } + return false; + } + + public static boolean hq_xz(List list) { + try { + File file = new File(wj.filesdri + "mp3"); + File[] files = file.listFiles(); + list.clear(); + for (File value : files) { + ID3v2 mp3File = new Mp3File(value).getId3v2Tag(); + String id = value.getName(); + String name = mp3File.getTitle(); + String zz = mp3File.getArtist(); + list.add(new MP3(id, name, zz, value.toString())); + } + return true; + } catch (Exception e) { + gj.sc("失败的错误 " + e); + } + return false; + } + + public static boolean hq_hc(List list) { + try { + String dqwb = wj.dqwb(wj.gd + "mp3_hc.json"); + if (dqwb == null) { + return false; + } + Type type = new TypeToken>() { + }.getType(); + Gson gson = new Gson(); + list.clear(); + list.addAll(gson.fromJson(dqwb, type)); + return true; + } catch (Exception e) { + gj.sc("失败的错误 " + e); + wj.sc(wj.gd + "mp3_hc.json"); + } + return false; + } + + + public static void hq_cd(Activity context, List list) { + boolean cd = wj.isCD(context); + if (!cd) { + return; + } + list.clear(); + try { + String absolutePath = Environment.getExternalStorageDirectory().getAbsolutePath(); + CD(new File(absolutePath), list); + } catch (Exception e) { + gj.sc("失败的错误 " + e); + } + } + + private static void CD(File file, List list) { + for (File a : file.listFiles()) { + if (a.isFile()) { + try { + // 创建一个 Mp3File 对象,用于读取 MP3 文件 + Mp3File mp3file = new Mp3File(a); + // 检查是否存在 ID3v2 标签 + if (mp3file.hasId3v2Tag()) { + // 获取 ID3v2 标签实例 + ID3v2 id3v2tag = mp3file.getId3v2Tag(); + MP3 mp3 = new MP3(a.toString(), id3v2tag.getTitle(), + id3v2tag.getArtist() + , a.toString()); + list.add(mp3); + } + } catch (Exception e) { + e.printStackTrace(); + } + } else if (a.isDirectory()) { + String string = a.getName(); + if (string.startsWith(".") || string.equals("Android") || string.equals("data")) { + continue; + } + CD(a, list); + } + } + } +} diff --git a/app/src/main/java/com/muqingbfq/api/resource.java b/app/src/main/java/com/muqingbfq/api/resource.java new file mode 100644 index 0000000..8901c74 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/api/resource.java @@ -0,0 +1,117 @@ +package com.muqingbfq.api; + +import android.text.TextUtils; + +import com.muqingbfq.main; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; +import com.muqingbfq.mq.wl; +import com.muqingbfq.XM; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.DecimalFormat; +import java.util.List; + +public class resource { + + public static void recommend(List list) { + try { + list.clear(); + JSONObject json; + String hq = wl.hq("/recommend/resource?cookie=" + wl.Cookie); + if (hq == null) { + hq = wj.dqwb(wj.gd_json); + if (hq != null) { + json = new JSONObject(hq); + if (json.getInt("code") == 200) { + wj.xrwb(wj.gd_json, hq); + JSONArray recommend = json.getJSONArray("recommend"); + int length = recommend.length(); + for (int i = 0; i < length; i++) { + JSONObject jsonObject = recommend.getJSONObject(i); + add(jsonObject, list); + } + } + } + return; + } + json = new JSONObject(hq); + if (json.getInt("code") == 200) { + wj.xrwb(wj.gd_json, hq); + JSONArray recommend = json.getJSONArray("recommend"); + int length = recommend.length(); + for (int i = 0; i < length; i++) { + JSONObject jsonObject = recommend.getJSONObject(i); + add(jsonObject, list); + } + } + } catch (Exception e) { + gj.sc("resource tuijian" + e); + } + } + + + public static XM Playlist_content(String UID) throws JSONException { + String hq = wl.get(main.api + "/playlist/detail?id=" + UID); + JSONObject js = new JSONObject(hq).getJSONObject("playlist"); + String id = js.getString("id"); + String name = js.getString("name"); + String coverImgUrl = js.getString("coverImgUrl"); + + long playCount = js.getLong("playCount"); + String formattedNumber = String.valueOf(playCount); + if (playCount > 9999) { + DecimalFormat df = new DecimalFormat("#,###.0万"); + formattedNumber = df.format(playCount / 10000); + } + String s = js.getInt("trackCount") + "首," + + "by " + js.getJSONObject("creator").getString("nickname") + + ",播放" + + formattedNumber + "次"; + return new XM(id, name, s, coverImgUrl); + } + +// 排行榜 + public static void leaderboard(List list) { + String hq; + try { + if (wj.cz(wj.gd_phb)) { + hq = wj.dqwb(wj.gd_phb); + } else { + hq = wl.hq("/toplist"); + if (hq == null) { + return; + } + wj.xrwb(wj.gd_phb, hq); + } + JSONObject jsonObject = new JSONObject(hq); + if (jsonObject.getInt("code") == 200) { + JSONArray list_array = jsonObject.getJSONArray("list"); + int length = list_array.length(); + for (int i = 0; i < length; i++) { + JSONObject get = list_array.getJSONObject(i); + String id = get.getString("id"); + String name = get.getString("name") + "\n"; + String description = get.getString("description"); + if (!TextUtils.isEmpty(description) && !description.equals("null")) { + name += description; + } + String coverImgUrl = get.getString("coverImgUrl"); + list.add(new XM(id, name, coverImgUrl)); + } + } + } catch (Exception e) { + gj.sc(e); + } + } + + private static void add(JSONObject jsonObject, List list) throws Exception { + String id = jsonObject.getString("id"); + String name = jsonObject.getString("name"); + String picUrl = jsonObject.getString("picUrl"); + list.add(new XM(id, name, picUrl)); + } +} diff --git a/app/src/main/java/com/muqingbfq/api/url.java b/app/src/main/java/com/muqingbfq/api/url.java new file mode 100644 index 0000000..faabf8e --- /dev/null +++ b/app/src/main/java/com/muqingbfq/api/url.java @@ -0,0 +1,114 @@ +package com.muqingbfq.api; + +import com.mpatric.mp3agic.ID3v2; +import com.mpatric.mp3agic.Mp3File; +import com.muqingbfq.MP3; +import com.muqingbfq.fragment.Media; +import com.muqingbfq.home; +import com.muqingbfq.main; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; +import com.muqingbfq.mq.wl; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class url extends Thread { + public static String api = "/song/url/v1"; + MP3 x; + + public url(MP3 x) { + this.x = x; + start(); + } + + public static MP3 hq(MP3 x) { +// gj.sc(x.id); + getLrc(x.id); + Media.loadLyric(); + try { + if (wj.cz(x.id)) { + x.url = x.id; + return x; + } else if (wj.cz(wj.mp3 + x.id)) { + x.url = wj.mp3 + x.id; + return x; + } else if (wj.cz(wj.filesdri + "hc/" + x.id)) { + x.url = wj.filesdri + "hc/" + x.id; + return x; + } + String level = "standard"; + boolean wiFiConnected = gj.isWiFiConnected(); + if (wiFiConnected) { + level = "exhigh"; + } + String hq = wl.hq(api + "?id=" + x.id + "&level=" + + level + "&cookie=" + wl.Cookie); + if (hq == null) { + return null; + } + JSONObject json = new JSONObject(hq); +// gj.sc(json); + if (json.getInt("code") == -460) { + String message = json.getString("message"); + gj.sc(message); + return null; + } + JSONArray data = json.getJSONArray("data"); + JSONObject jsonObject = data.getJSONObject(0); +// gj.sc(jsonObject.getString("url")); + x.url = jsonObject.getString("url"); + return x; + } catch (JSONException e) { + gj.sc("url hq :" + e); + } + return null; + } + + @Override + public void run() { + super.run(); + com.muqingbfq.bfqkz.mp3(hq(x)); + } + + + public static void getLrc(String id) { + String file = wj.mp3 + id; + boolean cz = wj.cz(id); + if (cz) { + file = id; + } + if (cz || wj.cz(file)) { + try { + Mp3File mp3file = new Mp3File(file); + if (mp3file.hasId3v2Tag()) { + ID3v2 id3v2Tag = mp3file.getId3v2Tag(); + com.muqingbfq.bfqkz.lrc = id3v2Tag.getLyrics(); + } + if (com.muqingbfq.bfqkz.lrc == null) { + com.muqingbfq.bfqkz.lrc = wl.hq("/lyric?id=" + id); + } + } catch (Exception e) { + gj.sc("url getlrc:" + e); + } + } else { + com.muqingbfq.bfqkz.lrc = wl.hq("/lyric?id=" + id); + } + } + + public static String Lrc(String id) { + return wl.hq("/lyric?id=" + id); + } + + public static String picurl(String id) { + String hq = wl.hq("/song/detail?ids=" + id); + try { + return new JSONObject(hq).getJSONArray("songs").getJSONObject(0) + .getJSONObject("al").getString("picUrl"); + } catch (Exception e) { + gj.sc("url picurl:" + e); + } + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/bfq.java b/app/src/main/java/com/muqingbfq/bfq.java new file mode 100644 index 0000000..6f3ffe8 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/bfq.java @@ -0,0 +1,603 @@ +package com.muqingbfq; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.LinearInterpolator; +import android.widget.SeekBar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.palette.graphics.Palette; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.Target; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.jaeger.library.StatusBarUtil; +import com.mpatric.mp3agic.ID3v2; +import com.mpatric.mp3agic.Mp3File; +import com.muqingbfq.api.FileDownloader; +import com.muqingbfq.databinding.ActivityBfqBinding; +import com.muqingbfq.fragment.Media; +import com.muqingbfq.mq.AppCompatActivity; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class bfq extends AppCompatActivity + implements GestureDetector.OnGestureListener { + public String lrc; + public MP3 mp3; + public boolean isplay = true; + GestureDetector gestureDetector; + int seekbarH = 0; + + private void lrc(View v) { + // 隐藏view2并显示view1的动画效果 + v.animate() + .alpha(0.0f) + .setDuration(500) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + v.setVisibility(View.GONE); + binding.cardview.setVisibility(View.VISIBLE); + binding.cardview.setAlpha(0.0f); + binding.cardview.animate().alpha(1.0f).setDuration(500).setListener(null); + } + }); + } + + @SuppressLint("ClickableViewAccessibility") + private void setLrc() { + DisplayMetrics dm = getResources().getDisplayMetrics(); + ViewGroup.LayoutParams layoutParams = binding.cardview.getLayoutParams(); + if (!gj.isTablet(this)) { + layoutParams.height = (int) (dm.widthPixels / 1.3f); + layoutParams.width = (int) (dm.widthPixels / 1.3f); + binding.lrcView.setOnClickListener(this::lrc); + + binding.lrcView.setOnSingerClickListener(() -> lrc(binding.lrcView)); +// binding.lrcView.setTextGravity(GRAVITY_LEFT) + } else { + layoutParams.height = (int) (dm.heightPixels / 2.0f); + layoutParams.width = (int) (dm.heightPixels / 2.0f); + } + binding.lrcView.setNormalTextSize(80f); + binding.lrcView.setCurrentTextSize(100f); + binding.lrcView.setTranslateTextScaleValue(0.8f); + binding.lrcView.setHorizontalOffset(-50f); + binding.lrcView.setHorizontalOffsetPercent(0.5f); + binding.lrcView.setItemOffsetPercent(0.5f); + binding.lrcView.setIsDrawTranslation(true); + binding.lrcView.setIsEnableBlurEffect(true); + binding.cardview.setLayoutParams(layoutParams); + + binding.lrcView.setDraggable(true, time -> { + bfqkz.mt.seekTo((int) time); + return false; + }); + binding.tdt.setThumb(null); + binding.tdt.post(() -> seekbarH = binding.tdt.getHeight()); + binding.tdt.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + // 当进度发生变化时执行操作 + setTime_b(bfq_an.getTime(progress)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // 当开始拖动滑块时执行操作 + // 创建 ValueAnimator 对象,实现进度条高度的平滑过渡 + ValueAnimator animator = ValueAnimator.ofInt(seekbarH, + seekbarH + 30); + animator.addUpdateListener(animation -> { + seekBar.getLayoutParams().height = (int) animation.getAnimatedValue(); + seekBar.requestLayout(); + }); + animator.setDuration(200); // 设置动画持续时间为 200 毫秒 + animator.start(); // 开始执行动画 + isplay = false; + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // 当停止拖动滑块时执行操作 + isplay = true; + // 创建 ValueAnimator 对象,实现恢复进度条高度的平滑过渡 + ValueAnimator animator = ValueAnimator.ofInt(seekbarH + 30, + seekbarH); + animator.addUpdateListener(animation -> { + seekBar.getLayoutParams().height = (int) animation.getAnimatedValue(); + seekBar.requestLayout(); + }); + animator.setDuration(200); // 设置动画持续时间为 200 毫秒 + animator.start(); // 开始执行动画 + bfqkz.mt.seekTo(seekBar.getProgress()); + } + }); + } + + public static void startactivity(Context context, MP3 mp3) { + gj.sc(mp3.toString()); + Intent intent = new Intent(context, bfq.class); + intent.putExtra("MP3", mp3); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + context.startActivity(intent); + } + + ObjectAnimator rotateAnimation; + + private void Animation() { + if (bfqkz.mt.isPlaying()) { + if (rotateAnimation.isPaused()) { + rotateAnimation.resume(); + } else { + rotateAnimation.start(); + } + } else { + rotateAnimation.pause(); + } + } + + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + StatusBarUtil.setTransparent(this); + + setContentView(); + gestureDetector = new GestureDetector(this, this); + setLrc(); + rotateAnimation = ObjectAnimator.ofFloat(binding.cardview + , "rotation", 0f, 360f); + rotateAnimation.setDuration(30000); // 设置动画持续时间,单位为毫秒 + rotateAnimation.setRepeatCount(ObjectAnimator.INFINITE); // 设置重复次数为无限 + rotateAnimation.setInterpolator(new LinearInterpolator()); // 设置插值器,这里使用线性插值器 + rotateAnimation.start(); + binding.kg.setOnClickListener(v -> { + if (bfqkz.mt.isPlaying()) { + bfqkz.mt.pause(); + setbf(false); + } else { + bfqkz.mt.start(); + setbf(true); + } + Animation(); + }); + binding.xyq.setOnClickListener(v -> bfq_an.xyq()); + binding.syq.setOnClickListener(v -> bfq_an.syq()); + + binding.image1.setOnClickListener(new toolbar()); + binding.image2.setOnClickListener(new toolbar()); + + binding.bfqListMp3. + setOnClickListener(view1 -> com.muqingbfq.fragment.bflb_db.start(this)); + binding.control.setOnClickListener(new bfq_an.control(binding.control)); + + binding.like.setOnClickListener(view1 -> { + try { + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + List list = gson.fromJson(wj.dqwb(wj.gd + "mp3_like.json"), type); + if (list == null) { + list = new ArrayList<>(); + } + if (bfqkz.like_bool) { + list.remove(bfqkz.xm); + setlike(false); + } else { + if (!list.contains(bfqkz.xm)) { + list.add(bfqkz.xm); + setlike(true); + } + } + bfqkz.like_bool = !bfqkz.like_bool; + wj.xrwb(wj.gd + "mp3_like.json", gson.toJson(list)); + } catch (Exception e) { + gj.sc(e); + } + }); + binding.download.setOnClickListener(view -> { + if (wj.cz(wj.mp3 + bfqkz.xm.id)) { + gj.ts(this, "你已经下载过这首歌曲了"); + return; + } + if (bfqkz.xm != null) { + new FileDownloader(bfq.this).downloadFile(bfqkz.xm); + } + }); + Intent intent = getIntent(); + mp3 = (MP3) intent.getSerializableExtra("MP3"); + new thread().start(); + binding.fragmentBfq.setOnTouchListener((v, event) -> { + gestureDetector.onTouchEvent(event); + if (event.getAction() == MotionEvent.ACTION_UP) { + if (binding.getRoot().getRootView().getTranslationY() > (getResources().getDisplayMetrics().heightPixels / 2.0f)) { + finish(); + return true; + } + ObjectAnimator animator = ObjectAnimator.ofFloat(binding.getRoot().getRootView() + , "y", binding.getRoot().getRootView().getTranslationY(), 0); + animator.setDuration(500); + animator.start(); + } + return true; + }); + + } + + private class toolbar implements View.OnClickListener { + @Override + public void onClick(View v) { + if (v.getId() == R.id.image1) { + finish(); + } else if (v.getId() == R.id.image2) { + com.muqingbfq.mq.gj.fx(v.getContext(), + "音乐名称:" + mp3.name + + "\n 作者:" + mp3.zz + + "\n 链接:https://music.163.com/#/song?id=" + mp3.id); + } + } + } + + class thread extends Thread { + @Override + public void run() { + super.run(); + if (mp3 != null) { + if (bfqkz.xm == null || !bfqkz.xm.equals(mp3)) { + bfqkz.xm = mp3; + bfqkz.mp3(com.muqingbfq.api.url.hq(mp3)); + } + } + if (binding == null) { + return; + } + main.handler.post(() -> { + if (mp3 != null) { + sx(); + } + setbf(bfqkz.mt.isPlaying()); + Animation(); + }); +// main.handler.post(runnable); + } + } + + public void sx() { + setname(mp3.name); + setzz(mp3.zz); + bfq_an.islike(); + int duration = bfqkz.mt.getDuration(); + setMax(duration); + gj.sc(duration); + setTime_a(bfq_an.getTime(duration)); + int position = bfqkz.mt.getCurrentPosition(); + Progress(position); + setImageBitmap(); + } + + public void setname(String str) { + binding.name.setText(str); + } + + public void setzz(String str) { + binding.zz.setText(str); + } + + public void kgsetImageResource(int a) { + if (binding == null) { + return; + } + binding.kg.setImageResource(a); + } + + @Override + protected ActivityBfqBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityBfqBinding.inflate(layoutInflater); + } + + public void setlike(boolean bool) { + if (bool) { + binding.like.setImageTintList(ContextCompat. + getColorStateList(binding.getRoot().getContext(), android.R.color.holo_red_dark)); + } else { + binding.like.setImageTintList(ColorStateList.valueOf(ColorTint)); + } + islike = bool; + } + + Runnable runnable = new Runnable() { + @Override + public void run() { + if (isplay) { + int position = bfqkz.mt.getCurrentPosition(); + Progress(position); + } + if (mp3 != null && !mp3.equals(bfqkz.xm) && binding != null) { + mp3 = bfqkz.xm; + setname(mp3.name); + setzz(mp3.zz); + bfq_an.islike(); + int duration = bfqkz.mt.getDuration(); + setMax(duration); + gj.sc(duration); + setTime_a(bfq_an.getTime(duration)); + int position = bfqkz.mt.getCurrentPosition(); + Progress(position); + setImageBitmap(); + } + if (bfqkz.mt.isPlaying() != isPlaying) { + + setbf(bfqkz.mt.isPlaying()); + } + if (bfqkz.like_bool != islike) { + setlike(bfqkz.like_bool); + } + if (!Objects.equals(bfqkz.lrc, lrc)) { + lrc = bfqkz.lrc; + String[] strings = Media.loadLyric(); + binding.lrcView.loadLyric(strings[0], strings[1]); + } + main.handler.postDelayed(this, 1000); // 每秒更新一次进度 + } + }; + + public boolean islike = false; + public boolean isPlaying = false; + + public void setbf(boolean bool) { + if (bool) { + //开始 + kgsetImageResource(R.drawable.bf); + } else { + //暂停 + kgsetImageResource(R.drawable.zt); + } + isPlaying = bool; + } + + public void setImageBitmap() { + if (binding == null) { + return; + } + if (wj.cz(bfqkz.xm.picurl)) { + try { + Mp3File mp3file = new Mp3File(bfqkz.xm.picurl); + if (mp3file.hasId3v2Tag()) { + ID3v2 id3v2Tag = mp3file.getId3v2Tag(); + byte[] albumImage = id3v2Tag.getAlbumImage(); + Bitmap bitmap = BitmapFactory. + decodeByteArray(albumImage, 0, albumImage.length); + binding.cardview.imageView.setImageBitmap(bitmap); + color(bitmap); + } + return; + } catch (Exception a) { + gj.sc(getClass() + " yc:" + a); + } + } + if (!isFinishing()) { + Glide.with(this) + .asBitmap() + .load(mp3.picurl) + .apply(new RequestOptions() + .placeholder(R.drawable.ic_launcher_foreground) + .error(R.drawable.ic_launcher_foreground)) + .addListener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(@NonNull Bitmap resource, + @NonNull Object model, Target target, + @NonNull DataSource dataSource, + boolean isFirstResource) { + color(resource); + binding.cardview.imageView.setImageBitmap(resource); + return true; + } + }).into(binding.cardview.imageView); + } + + } + + private void color(Bitmap bitmap) { + Palette.Builder builder = new Palette.Builder(bitmap); + builder.generate(palette -> { +// 获取图片中柔和的亮色 + int lightMutedColor = palette.getLightMutedColor(Color.GRAY); + Palette.Swatch vibrantSwatch = palette.getLightVibrantSwatch(); + if (vibrantSwatch != null) { + int bodyTextColor = vibrantSwatch.getBodyTextColor(); + binding.lrcView.setCurrentColor(bodyTextColor); + binding.lrcView.setTimelineTextColor(bodyTextColor); +// 计算半亮度的颜色(直接将RGB分量除以2并向下取整) + int halfBrightnessColor = (bodyTextColor & 0x00FFFFFF) / 2; + binding.lrcView.setNormalColor(halfBrightnessColor); + + GradientDrawable gradientDrawable = new GradientDrawable( + GradientDrawable.Orientation.BOTTOM_TOP, // 渐变方向:从上到下 + new int[]{vibrantSwatch.getRgb(), lightMutedColor} // 渐变颜色数组 + ); + setTint(vibrantSwatch.getTitleTextColor()); + gradientDrawable.setShape(GradientDrawable.RECTANGLE); + binding.getRoot().setBackground(gradientDrawable); + } else { + int color = palette.getLightVibrantColor(Color.WHITE); + int titleTextColor = palette.getVibrantColor(Color.GRAY); + binding.lrcView.setCurrentColor(titleTextColor); + binding.lrcView.setTimelineTextColor(titleTextColor); +// 计算半亮度的颜色(直接将RGB分量除以2并向下取整) + int halfBrightnessColor = (titleTextColor & 0x00FFFFFF) / 2; + binding.lrcView.setNormalColor(halfBrightnessColor); + + GradientDrawable gradientDrawable = new GradientDrawable( + GradientDrawable.Orientation.BOTTOM_TOP, // 渐变方向:从上到下 + new int[]{color, lightMutedColor} // 渐变颜色数组 + ); + setTint(titleTextColor); + gradientDrawable.setShape(GradientDrawable.RECTANGLE); + binding.getRoot().setBackground(gradientDrawable); + } + }); + } + + private int ColorTint = Color.WHITE; + + private void setTint(int color) { + this.ColorTint = color; + ColorStateList colorStateList = ColorStateList.valueOf(color); + binding.kg.setImageTintList(colorStateList); + binding.syq.setImageTintList(colorStateList); + binding.xyq.setImageTintList(colorStateList); + binding.bfqListMp3.setImageTintList(colorStateList); + binding.control.setImageTintList(colorStateList); + binding.like.setImageTintList(colorStateList); + binding.download.setImageTintList(colorStateList); + binding.image2.setImageTintList(colorStateList); + binding.name.setTextColor(color); + binding.zz.setTextColor(color); + binding.timeA.setTextColor(color); + binding.timeB.setTextColor(color); + + Drawable progressDrawable = binding.tdt.getProgressDrawable(); + LayerDrawable layerDrawable = (LayerDrawable) progressDrawable; + Drawable progress = layerDrawable.findDrawableByLayerId(android.R.id.progress); + progress.setColorFilter(color, PorterDuff.Mode.SRC_IN); +// 设置进度条背景的颜色 +/* Drawable background = layerDrawable.findDrawableByLayerId(android.R.id.background); + background.setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_IN);*/ + } + + @Override + public void finish() { + super.finish(); + } + + @Override + protected void onStart() { + super.onStart(); + main.handler.post(runnable); + } + + @Override + protected void onStop() { + super.onStop(); + main.handler.removeCallbacks(runnable); + } + + public void setTime_a(String str) { + binding.timeA.setText(str); + } + + public void setTime_b(String str) { + binding.timeB.setText(str); + } + + public void setMax(int max) { + binding.tdt.setMax(Math.max(0, max)); + } + + public void Progress(int progress) { + int min = Math.min(progress, binding.tdt.getMax()); + binding.tdt.setProgress(min); + binding.lrcView.updateTime(min, true); + } + + @Override + public boolean onDown(@NonNull MotionEvent e) { + return false; + } + + @Override + public void onShowPress(@NonNull MotionEvent e) { + + } + + @Override + public boolean onSingleTapUp(@NonNull MotionEvent e) { + // 判断是哪个视图被点击了 + if (!gj.isTablet(this)) { + switchViews(binding.cardview, binding.lrcView); + } + return true; + } + + private void switchViews(final View view1, final View view2) { + // 隐藏view1并显示view2的动画效果 + if (binding.cardview.getVisibility() == View.VISIBLE) { + view1.animate() + .alpha(0.0f) + .setDuration(500) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view1.setVisibility(View.GONE); + view2.setVisibility(View.VISIBLE); + view2.setAlpha(0.0f); + view2.animate().alpha(1.0f).setDuration(500).setListener(null); + } + }); + } + } + + // 判断触摸点是否在视图范围内的辅助方法 + @Override + public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, + float distanceX, float distanceY) { + float y = binding.getRoot().getRootView().getTranslationY() - distanceY; + y = Math.max(0, y); + //移动的距离 + int heightPixels = getResources().getDisplayMetrics().heightPixels; + if (y > heightPixels - heightPixels / 5.0) { + finish(); + return true; + } + binding.getRoot().getRootView().setTranslationY(y); + return true; + } + + @Override + public void onLongPress(@NonNull MotionEvent e) { + + } + + @Override + public boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, + float velocityX, float velocityY) { + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/bfq_an.java b/app/src/main/java/com/muqingbfq/bfq_an.java new file mode 100644 index 0000000..b12bdcb --- /dev/null +++ b/app/src/main/java/com/muqingbfq/bfq_an.java @@ -0,0 +1,141 @@ +package com.muqingbfq; + +import android.view.View; +import android.widget.ImageView; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.muqingbfq.api.url; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; + +import java.lang.reflect.Type; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +public class bfq_an { + + public static void syq() { + if (bfqkz.list.isEmpty()) { + return; + } + bfqkz.mt.pause(); + int i = bfqkz.list.indexOf(bfqkz.xm) - 1; + if (i < 0) { + i = 0; + } + bfqkz.xm = bfqkz.list.get(i); + new url(bfqkz.xm); + } + + public static void xyq() { + if (bfqkz.list.isEmpty()) { + return; + } + bfqkz.mt.pause(); +// gj.sc(bfqkz.list.get(bfqkz.getmti())); + new url(bfqkz.list.get(bfqkz.getmti())); + } + + public static class control implements View.OnClickListener { + ImageView imageView; + + public control(ImageView imageView) { + this.imageView = imageView; + setImage(-1); + } + + private void setImage(int i) { + gj.sc(bfqkz.ms); + + bfqkz.mt.setLooping(bfqkz.ms == 0); + switch (bfqkz.ms) { + case 0: + imageView.setImageResource(R.drawable.mt_xh); + if (i == -1) { + break; + } + if (!bfqkz.list_baocun.isEmpty()) { + bfqkz.list.clear(); + bfqkz.list.addAll(bfqkz.list_baocun); + } + break; + case 1: + imageView.setImageResource(R.drawable.mt_sx); + if (i == -1) { + break; + } + if (!bfqkz.list_baocun.isEmpty()) { + bfqkz.list.clear(); + bfqkz.list.addAll(bfqkz.list_baocun); + } + break; + case 2: + imageView.setImageResource(R.drawable.mt_sj); + if (i == -1) { + break; + } + bfqkz.list_baocun.clear(); + bfqkz.list_baocun.addAll(bfqkz.list); + Collections.shuffle(bfqkz.list); + break; + } + } + + @Override + public void onClick(View v) { + if (bfqkz.ms == 2) { + bfqkz.ms = 0; + } else { + bfqkz.ms++; + } + setImage(bfqkz.ms); + main.edit.putInt("ms", bfqkz.ms); + main.edit.commit(); + } + } + + static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss", Locale.CHINA); + public static String getTime(long time) { + return simpleDateFormat.format(new Date(time)); + } + + public static boolean islike() { + boolean contains = false; + String dqwb = wj.dqwb(wj.gd + "mp3_like.json"); + if (dqwb != null) { + try { + Type type = new TypeToken>() { + }.getType(); + List o = new Gson().fromJson(dqwb, type); + if (o != null) { + contains = o.contains(bfqkz.xm); + } + } catch (Exception e) { + wj.sc(wj.gd + "mp3_like.json"); + } + } + return bfqkz.like_bool = contains; + } + + public static boolean getlike(MP3 xm) { + boolean contains = false; + String dqwb = wj.dqwb(wj.gd + "mp3_like.json"); + if (dqwb != null) { + try { + Type type = new TypeToken>() { + }.getType(); + List o = new Gson().fromJson(dqwb, type); + if (o != null) { + contains = o.contains(xm); + } + } catch (Exception e) { + wj.sc(wj.gd + "mp3_like.json"); + } + } + return contains; + } +} diff --git a/app/src/main/java/com/muqingbfq/bfqkz.java b/app/src/main/java/com/muqingbfq/bfqkz.java new file mode 100644 index 0000000..91e6a26 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/bfqkz.java @@ -0,0 +1,190 @@ +package com.muqingbfq; + +import android.annotation.SuppressLint; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.support.v4.media.MediaBrowserCompat; +import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media.MediaBrowserServiceCompat; + +import com.muqingbfq.api.url; +import com.muqingbfq.mq.BluetoothMusicController; +import com.muqingbfq.mq.gj; + +import java.util.ArrayList; +import java.util.List; + +public class bfqkz extends MediaBrowserServiceCompat { + public final static MediaPlayer mt = new MediaPlayer(); + + public static List list = new ArrayList<>(); + //保存原始list顺序 + public static List list_baocun = new ArrayList<>(); + public static List lishi_list = new ArrayList<>(); + public static int ms; + // 0 循环 1 顺序 2 随机 + public static MP3 xm; + public static boolean like_bool; + public static String lrc; + @SuppressLint("StaticFieldLeak") + public static com.muqingbfq.mq.NotificationManagerCompat notify; + + public static int getmti() { + if (xm == null) { + return 0; + } + int i = bfqkz.list.indexOf(xm) + 1; + if (i >= bfqkz.list.size()) { + i = 0; + } + return i; + } + + public static void mp3(MP3 mp3) { + try { + if (mp3 == null) { + return; + } + if (TextUtils.isEmpty(mp3.url)) { + //针对错误进行相应的处理 + bfqkz.list.remove(bfqkz.xm); + bfq_an.xyq(); + return; + } + if (TextUtils.isEmpty(mp3.picurl)) { + mp3.picurl = url.picurl(mp3.id); + } + mt.setDataSource(mp3); + } catch (Exception e) { + gj.sc("bfqkz mp3(" + mp3 + ") :" + e); + } + } + + public static class mp3 extends Thread { + MP3 mp3; + + public mp3(MP3 mp3) { + this.mp3 = mp3; + start(); + } + + @Override + public void run() { + super.run(); + mp3(mp3); + } + } + + + public MediaSessionCompat mSession; + public PlaybackStateCompat.Builder playback; + + public PendingIntent pendingIntent; + + public MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); + + @Override + public void onCreate() { + super.onCreate(); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(new ComponentName(this, home.class));//用ComponentName得到class对象 + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);// 关键的一步,设置启动模式,两种情况 + pendingIntent = com.muqingbfq.mq.NotificationManagerCompat.getActivity(this, intent); + com.muqingbfq.api.playlist.hq_hc(bfqkz.lishi_list); + new BluetoothMusicController(this); + mSession = new MediaSessionCompat(this, "MediaSessionCompat", + home.componentName, pendingIntent); + playback = new PlaybackStateCompat.Builder(); + playback.setState(PlaybackStateCompat.STATE_NONE, 0, 1.0f) + .build(); + mSession.setCallback(new callback()); + mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); + + playback.setActions(PlaybackStateCompat.ACTION_PLAY); + playback.setActions(PlaybackStateCompat.ACTION_STOP); + + mSession.setPlaybackState(playback.build()); + setSessionToken(mSession.getSessionToken()); + mSession.setActive(true); + notify = new com.muqingbfq.mq.NotificationManagerCompat(this); + } + + class callback extends MediaSessionCompat.Callback { + @Override + public void onPlay() { + super.onPlay(); + if (playback.build().getState() == PlaybackStateCompat.STATE_PAUSED) { + mt.start(); + playback.setState(PlaybackStateCompat.STATE_PLAYING, 0, 1.0f) + .build(); + mSession.setPlaybackState(playback.build()); + } + gj.sc(this.getClass()); + + } + + @Override + public void onPause() { + super.onPause(); + if (playback.build().getState() == PlaybackStateCompat.STATE_PLAYING) { + mt.pause(); + playback.setState(PlaybackStateCompat.STATE_PAUSED, 0, 1.0f) + .build(); + mSession.setPlaybackState(playback.build()); + } + } + + @SuppressLint("SwitchIntDef") + @Override + public void onPlayFromUri(Uri uri, Bundle extras) { + try { + switch (playback.build().getState()) { + case PlaybackStateCompat.STATE_PLAYING: + case PlaybackStateCompat.STATE_PAUSED: + case PlaybackStateCompat.STATE_NONE: +// mp3(uri);/ + playback.setState(PlaybackStateCompat.STATE_CONNECTING, 0, 1.0f) + .build(); + mSession.setPlaybackState(playback.build()); + //我们可以保存当前播放音乐的信息,以便客户端刷新UI + mSession.setMetadata(new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, extras.getString("title")) + .build() + ); + break; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Nullable + @Override + public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { + return null; + } + + @Override + public void onLoadChildren(@NonNull String parentId, @NonNull Result> result) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/clean/fragment_clean.java b/app/src/main/java/com/muqingbfq/clean/fragment_clean.java new file mode 100644 index 0000000..45d825c --- /dev/null +++ b/app/src/main/java/com/muqingbfq/clean/fragment_clean.java @@ -0,0 +1,133 @@ +package com.muqingbfq.clean; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.text.format.Formatter; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.muqingbfq.R; +import com.muqingbfq.databinding.ActivityCleanBinding; +import com.muqingbfq.mq.FragmentActivity; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class fragment_clean extends FragmentActivity { + List list = new ArrayList<>(); + List list_box = new ArrayList<>(); + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(); + setToolbar(); + UI(); + } + + private void UI() { + list.clear(); + list.add(new String[]{"下载的音乐", wj.mp3}); + list.add(new String[]{"下载的歌单",wj.gd}); + list.add(new String[]{"缓存的音乐",wj.filesdri+"hc"}); + list.add(new String[]{"内部缓存", getCacheDir().toString()}); + String s = Glide.getPhotoCacheDir(this).toString(); + list.add(new String[]{"Glide缓存", s}); + binding.toolbar.setTitle("储存清理"); + binding.recyclerview.setAdapter(adapter); + } + + private final RecyclerView.Adapter adapter = new RecyclerView.Adapter() { + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View inflate = LayoutInflater.from(fragment_clean.this). + inflate(R.layout.list_clean, parent, false); + return new VH(inflate); + } + + @SuppressLint({"ClickableViewAccessibility", "SetTextI18n"}) + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + String[] s = list.get(position); + File file = new File(s[1]); + long leng = 0; + int size = 0; + if (file.isDirectory()) { + gj.sc(file.toString()); + for (File a : file.listFiles()) { + leng += a.length(); + size++; + } + } else { + holder.checkBox.setEnabled(false); + } + String s1 = Formatter.formatFileSize(fragment_clean.this, leng); + holder.checkBox.setText(s[0] + ":" + s1 + " 共计:" + size+" 个文件"); + holder.checkBox.setOnCheckedChangeListener((compoundButton, b) -> { + if (b) { + list_box.add(file.toString()); + }else { + list_box.remove(file.toString()); + } + menu_deleat.setVisible(list_box.size() > 0); + }); + } + + @Override + public int getItemCount() { + return list.size(); + } + }; + + class VH extends RecyclerView.ViewHolder { + public CheckBox checkBox; + public VH(@NonNull View itemView) { + super(itemView); + checkBox = itemView.findViewById(R.id.box); + } + } + + MenuItem menu_deleat; + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu_deleat = menu.add("删除"); + menu_deleat.setIcon(R.drawable.delete); + menu_deleat.setTitle("删除"); + menu_deleat.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + menu_deleat.setVisible(false); + return super.onCreateOptionsMenu(menu); + } + + @Override + protected ActivityCleanBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityCleanBinding.inflate(layoutInflater); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + } else if (item == menu_deleat) { + for (int i = 0; i < list_box.size(); i++) { + File s= new File(list_box.get(i)); + wj.sc(s); + } + list_box.clear(); + menu_deleat.setVisible(false); + UI(); + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/com/muqingbfq/fragment/Media.java b/app/src/main/java/com/muqingbfq/fragment/Media.java new file mode 100644 index 0000000..eb445a4 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/Media.java @@ -0,0 +1,29 @@ +package com.muqingbfq.fragment; + +import com.dirror.lyricviewx.LyricViewX; +import com.muqingbfq.mq.gj; + +import org.json.JSONObject; + +public class Media { + + public static String[] loadLyric() { + if (com.muqingbfq.bfqkz.lrc == null) { + return null; + } + JSONObject jsonObject; + String a = null, b = null; + try { + jsonObject = new JSONObject(com.muqingbfq.bfqkz.lrc); + a = jsonObject.getJSONObject("lrc").getString("lyric"); + b = jsonObject.getJSONObject("tlyric").getString("lyric"); + } catch (Exception e) { + gj.sc("Media loadLyric " + e); + } + LyricViewX.lrc(a, b); +// gj.sc(LyricViewX.lyricEntryList.get(0).text); + + return new String[]{a, b}; + } + +} diff --git a/app/src/main/java/com/muqingbfq/fragment/bflb_db.java b/app/src/main/java/com/muqingbfq/fragment/bflb_db.java new file mode 100644 index 0000000..792bb7d --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/bflb_db.java @@ -0,0 +1,139 @@ +package com.muqingbfq.fragment; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.muqingbfq.MP3; +import com.muqingbfq.R; +import com.muqingbfq.api.url; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.FragmentBflbDbBinding; +import com.muqingbfq.databinding.ListMp3ABinding; +import com.muqingbfq.list.MyViewHoder; +import com.muqingbfq.yc; + +import java.util.Collections; + +public class bflb_db extends BottomSheetDialog { + public static RecyclerView.Adapter adapter; + FragmentBflbDbBinding binding; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = FragmentBflbDbBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + try { + binding.lb.setAdapter(new spq()); + if (bfqkz.xm != null) { + binding.lb.smoothScrollToPosition(getI()); + } + binding.textView.setOnClickListener(v -> { + if (bfqkz.xm != null) { + binding.lb.smoothScrollToPosition(getI()); + } + }); + binding.sc.setOnClickListener(view -> new MaterialAlertDialogBuilder(getContext()) + .setTitle("清空播放列表") + .setPositiveButton("确定", (dialogInterface, i) -> { + bfqkz.list.clear(); + dismiss(); + }) + .setNegativeButton("取消", null) + .show()); + + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback( + ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) { + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { + int fromPosition = viewHolder.getAdapterPosition(); + int toPosition = target.getAdapterPosition(); + // 在这里处理数据集的移动 + Collections.swap(bfqkz.list,fromPosition,toPosition); + adapter.notifyItemMoved(fromPosition, toPosition); + return true; // 返回true表示已经处理了拖动 + } + + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { + // 不处理滑动操作 + } + }); + itemTouchHelper.attachToRecyclerView(binding.lb); + + } catch (Exception e) { + yc.start(getContext(), e); + } + } + + private int getI() { + int i = bfqkz.list.indexOf(bfqkz.xm); + if (i == -1) { + i = 0; + } + return i; + } + public bflb_db(Context context) { + super(context); + } + + public static void start(Context context) { + new bflb_db(context).show(); + } + + private class spq extends RecyclerView.Adapter { + public spq() { + adapter = this; + } + @NonNull + @Override + public MyViewHoder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new MyViewHoder(ListMp3ABinding. + inflate(getLayoutInflater(),parent,false)); + } + @Override + public void onBindViewHolder(@NonNull MyViewHoder holder, int position) { + MP3 x = bfqkz.list.get(position); + holder.bindingA.name.setText(x.name); + holder.bindingA.zz.setText(String.format(" · %s", x.zz)); + int color = ContextCompat.getColor(holder.getContext(), R.color.text); + if (bfqkz.xm != null && x.id.equals(bfqkz.xm.id)) { + color = ContextCompat.getColor(holder.getContext(), R.color.text_cz); + } + holder.bindingA.name.setTextColor(color); + holder.bindingA.zz.setTextColor(color); + holder.itemView.setOnClickListener(view -> { + if (bfqkz.xm != x) { + bfqkz.xm = x; + new url(x); + } + }); + holder.bindingA.delete.setOnClickListener(v -> { + bfqkz.list.remove(holder.getAdapterPosition()); + notifyItemRemoved(holder.getAdapterPosition()); + }); + } + @Override + public int getItemCount() { +// binding.textView.setText(String.valueOf(bfqkz.list.size())); + return bfqkz.list.size(); + } + } + + @Override + public void dismiss() { + super.dismiss(); + adapter = null; + } +} diff --git a/app/src/main/java/com/muqingbfq/fragment/bfq_db.java b/app/src/main/java/com/muqingbfq/fragment/bfq_db.java new file mode 100644 index 0000000..bf61dfb --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/bfq_db.java @@ -0,0 +1,152 @@ +package com.muqingbfq.fragment; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.os.Handler; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.muqingbfq.MP3; +import com.muqingbfq.R; +import com.muqingbfq.bfq; +import com.muqingbfq.bfq_an; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.FragmentBfqDbBinding; + +import java.util.Objects; + +public class bfq_db extends Fragment implements GestureDetector.OnGestureListener { + FragmentBfqDbBinding binding; + private GestureDetector gestureDetector; + + @SuppressLint("ClickableViewAccessibility") + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding = FragmentBfqDbBinding.inflate(inflater, container, false); + // 获取当前活动的主题 + binding.kg.setOnClickListener(v -> { + if (bfqkz.mt.isPlaying()) { + bfqkz.mt.pause(); + } else { + bfqkz.mt.start(); + } + setkg(bfqkz.mt.isPlaying()); + }); + binding.txb.setOnClickListener(view -> bflb_db.start(getContext())); + gestureDetector = new GestureDetector(getContext(), this); + binding.getRoot().setOnTouchListener((view, motionEvent) -> { + gestureDetector.onTouchEvent(motionEvent); + if (motionEvent.getAction() == MotionEvent.ACTION_UP) { + binding.linearLayout.setTranslationX(0); +// binding.getRoot().setAlpha(1.0f); + } else if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { +// binding.getRoot().setAlpha(0.2f); + } + return false; + }); + return binding.getRoot(); + } + + @Override + public void onResume() { + super.onResume(); + handler.post(runnable); + } + + @Override + public void onPause() { + super.onPause(); + handler.removeCallbacks(runnable); + } + + MP3 mp3; + boolean isPlaying = false, isvisible = false; + Handler handler = new Handler(); + Runnable runnable = new Runnable() { + @Override + public void run() { + if (!Objects.equals(mp3, bfqkz.xm)) { + mp3 = bfqkz.xm; + setname(mp3.name, " - " + mp3.zz); + } + if (bfqkz.mt.isPlaying() != isPlaying) { + setkg(bfqkz.mt.isPlaying()); + } + if (isvisible != bfqkz.list.isEmpty()) { + isvisible = bfqkz.list.isEmpty(); + if (isvisible) { + binding.getRoot().setVisibility(View.GONE); + } else { + binding.getRoot().setVisibility(View.VISIBLE); + } + } + handler.postDelayed(this, 1000); + } + }; + + public void setkg(boolean bool) { + if (bool) { + binding.kg.setImageResource(R.drawable.bf); + } else { + binding.kg.setImageResource(R.drawable.zt); + } + isPlaying = bool; + } + + public void setname(String a,String b) { + binding.textview1.setText(a); + binding.textview2.setText(b); + } + + @Override + public boolean onDown(@NonNull MotionEvent motionEvent) { + return true; + } + + @Override + public void onShowPress(@NonNull MotionEvent motionEvent) { + } + + @Override + public boolean onSingleTapUp(@NonNull MotionEvent motionEvent) { + bfq.startactivity(getContext(), bfqkz.xm); + return true; + } + + @Override + public boolean onScroll(@Nullable MotionEvent motionEvent, @NonNull MotionEvent motionEvent1, + float v, float v1) { + binding.linearLayout.setTranslationX(binding.linearLayout.getTranslationX() - v); + return false; + } + + @Override + public void onLongPress(@NonNull MotionEvent motionEvent) { + } + + @Override + public boolean onFling(@Nullable MotionEvent e1, + @NonNull MotionEvent e2, float v, float v1) { + float distance = e1.getX() - e2.getX(); + float threshold = getResources().getDisplayMetrics().widthPixels / 2.0f; + // 判断手势方向并限制滑动距离 + if (distance > threshold) { + // 向左滑动 + // 在这里添加你的逻辑代码 + bfq_an.xyq(); + } else if (distance < -threshold) { + // 向右滑动 + // 在这里添加你的逻辑代码 + bfq_an.syq(); + } + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/fragment/gd.java b/app/src/main/java/com/muqingbfq/fragment/gd.java new file mode 100644 index 0000000..77f56a9 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/gd.java @@ -0,0 +1,352 @@ +package com.muqingbfq.fragment; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityOptions; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.palette.graphics.Palette; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.Target; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.muqingbfq.R; +import com.muqingbfq.XM; +import com.muqingbfq.api.playlist; +import com.muqingbfq.api.resource; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.ActivityGdBinding; +import com.muqingbfq.databinding.ListGdBBinding; +import com.muqingbfq.databinding.ListGdBinding; +import com.muqingbfq.main; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; +import com.muqingbfq.mq.wl; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class gd extends com.muqingbfq.mq.FragmentActivity { + public static String gdid; + private final List list = new ArrayList<>(); + public baseadapter adapter; + int k; + + public static void start(Activity context, String[] str, View view) { + ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(context, + view, "text"); + Intent intent = new Intent(context, gd.class); + intent.putExtra("id", str[0]); + intent.putExtra("name", str[1]); + context.startActivity(intent, options.toBundle()); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getViewBinding().getRoot()); + setToolbar(); + + + Intent intent = getIntent(); + binding.title.setText(intent.getStringExtra("name")); + adapter = new baseadapter(this, list); + k = (int) (getResources().getDisplayMetrics().widthPixels / getResources().getDisplayMetrics().density + 0.5f); + + GridLayoutManager gridLayoutManager = new GridLayoutManager(this, k / 120); + binding.lb.setLayoutManager(gridLayoutManager); + binding.lb.setAdapter(adapter); + String id = intent.getStringExtra("id"); + new start(id); + } + + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + ActivityCompat.finishAfterTransition(this); + } + return true; + } + + @Override + protected ActivityGdBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityGdBinding.inflate(layoutInflater); + } + + @SuppressLint("NotifyDataSetChanged") + class start extends Thread { + String id; + + public start(String id) { + binding.recyclerview1Bar.setVisibility(View.VISIBLE); + this.id = id; + list.clear(); + start(); + } + + @Override + public void run() { + super.run(); + if (id.equals("排行榜")) { + resource.leaderboard(list); + } else { + String hq = wl.hq("/search?keywords=" + id + "&limit=" + (k * 3) + "&type=1000"); + try { + JSONArray jsonArray = new JSONObject(hq).getJSONObject("result") + .getJSONArray("playlists"); + int length = jsonArray.length(); + for (int i = 0; i < length; i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String id = jsonObject.getString("id"); + String name = jsonObject.getString("name"); + String coverImgUrl = jsonObject.getString("coverImgUrl"); + list.add(new XM(id, name, coverImgUrl)); + } + } catch (Exception e) { + gj.sc(e); + } + } + main.handler.post(() -> { + adapter.notifyDataSetChanged(); + binding.recyclerview1Bar.setVisibility(View.GONE); + if (list.isEmpty()) { + binding.recyclerview1Text.setVisibility(View.VISIBLE); + binding.recyclerview1Text.setOnClickListener(v -> new start(id)); + } else { + binding.recyclerview1Text.setVisibility(View.GONE); + } + }); + } + } + + public static class baseadapter extends RecyclerView.Adapter { + Activity context; + public List list; + + public baseadapter(Activity context, List list) { + this.context = context; + this.list = list; + } + + boolean bool = false; + + public baseadapter(Activity context, List list, boolean bool) { + this.context = context; + this.list = list; + this.bool = bool; + } + + public void setonlong(int position) { + XM xm = list.get(position); + gj.sc(xm.name); + String[] stringArray = context.getResources() + .getStringArray(R.array.gd_list); + if (!wj.cz(wj.gd + xm.id)) { + stringArray = new String[]{"下载歌单"}; + } + String[] finalStringArray = stringArray; + new MaterialAlertDialogBuilder(context). + setItems(stringArray, (dialog, id) -> { + switch (finalStringArray[id]) { + case "下载歌单": + new Thread() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + String hq = playlist.gethq(xm.id); + if (hq != null) { + try { + XM fh = resource.Playlist_content(xm.id); + JSONObject json = new JSONObject(hq); + json.put("name", fh.name); + json.put("picUrl", fh.picurl); + json.put("message", fh.message); +// json.put(fh.id, json); + wj.xrwb(wj.gd + xm.id, json.toString()); + wode.addlist(fh); + main.handler.post(() -> notifyItemChanged(position)); + } catch (JSONException e) { + gj.sc("list gd onclick thear " + e); + } + } + } + }.start(); + break; + case "删除歌单": +// 删除项目 + try { + wj.sc(wj.gd + xm.id); + wode.removelist(xm); + } catch (Exception e) { + gj.sc(e); + } + break; + } + // 在这里处理菜单项的点击事件 + }).show(); + } + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (bool) { + return new VH(ListGdBBinding.bind(LayoutInflater.from(context) + .inflate(R.layout.list_gd_b, parent, false))); + } + return new VH(ListGdBinding.bind(LayoutInflater.from(context) + .inflate(R.layout.list_gd, parent, false))); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + XM xm = list.get(position); + holder.itemView.setOnClickListener(new CARD(position)); + Drawable color_kg = ContextCompat.getDrawable(context, R.drawable.zt); + if (xm.id.equals(gdid)) { + color_kg = ContextCompat.getDrawable(context, R.drawable.bf); + } + if (bool) { + holder.bindingB.text1.setText(xm.name); + holder.bindingB.text2.setText(xm.message); + Glide.with(holder.itemView.getContext()) + .load(xm.picurl) + .apply(new RequestOptions() + .placeholder(R.drawable.ic_launcher_foreground) + .error(R.drawable.ic_launcher_foreground)) + .into(holder.bindingB.image); + holder.bindingB.kg.setOnClickListener(new KG(this, xm.id)); + } else { + holder.binding.text1.setText(xm.name); + holder.binding.kg.setImageDrawable(color_kg); + holder.binding.kg.setOnClickListener(new KG(this, xm.id)); + Glide.with(holder.itemView.getContext()) + .asBitmap() + .load(xm.picurl) + .addListener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(@NonNull Bitmap resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { + Palette.from(resource).generate(palette -> { + int color = palette.getLightMutedColor(Color.WHITE); + GradientDrawable gradientDrawable = new GradientDrawable( + GradientDrawable.Orientation.BOTTOM_TOP, + new int[]{color, color}); + gradientDrawable.setAlpha(128); + holder.binding.text1.setBackground(gradientDrawable); + holder.binding.getRoot().setRippleColor(ColorStateList.valueOf(color)); + }); + holder.binding.image.setImageBitmap(resource); + return true; + } + }) + .into(holder.binding.image); + } + holder.itemView.setOnLongClickListener(v -> { + setonlong(position); + return false; + }); + } + + class KG implements View.OnClickListener { + RecyclerView.Adapter adapter; + String id; + + public KG(RecyclerView.Adapter adapter, String id) { + this.adapter = adapter; + this.id = id; + + } + + @Override + public void onClick(View v) { + new Thread() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + super.run(); + boolean an = playlist.hq(bfqkz.list, id); + if (bfqkz.ms == 2) { + Collections.shuffle(bfqkz.list); + } + main.handler.post(() -> { + if (an) { + com.muqingbfq.bfq_an.xyq(); + ((ImageView) v).setImageResource(R.drawable.bf); + com.muqingbfq.fragment.gd.gdid = id; + } + adapter.notifyDataSetChanged(); + }); + } + }.start(); + } + } + + @Override + public int getItemCount() { + return list.size(); + } + + class CARD implements View.OnClickListener { + int position; + + public CARD(int position) { + this.position = position; + } + + @Override + public void onClick(View view) { + XM xm = list.get(position); + mp3.start(context, new String[]{xm.id, xm.name}, view); + } + } + + } + + static class VH extends RecyclerView.ViewHolder { + public ListGdBinding binding; + + public VH(@NonNull ListGdBinding itemView) { + super(itemView.getRoot()); + binding = itemView; + } + + ListGdBBinding bindingB; + + public VH(@NonNull ListGdBBinding itemView) { + super(itemView.getRoot()); + bindingB = itemView; + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/fragment/gd_adapter.java b/app/src/main/java/com/muqingbfq/fragment/gd_adapter.java new file mode 100644 index 0000000..e264a48 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/gd_adapter.java @@ -0,0 +1,117 @@ +package com.muqingbfq.fragment; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.muqingbfq.MP3; +import com.muqingbfq.R; +import com.muqingbfq.XM; +import com.muqingbfq.activity_search; +import com.muqingbfq.adapter.AdapterMp3; +import com.muqingbfq.api.resource; +import com.muqingbfq.databinding.FragmentGdBinding; +import com.muqingbfq.databinding.ListMp3ImageBinding; +import com.muqingbfq.main; +import com.muqingbfq.mq.Fragment; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; +import com.muqingbfq.mq.wl; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class gd_adapter extends Fragment { + List list = new ArrayList<>(); + AdapterMp3 adapterMp3 = new AdapterMp3(); + + + + @Override + protected FragmentGdBinding inflateViewBinding(LayoutInflater inflater, ViewGroup container) { + return FragmentGdBinding.inflate(inflater, container, false); + } + + + @Override + public void setUI(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(),LinearLayoutManager.HORIZONTAL,false); + binding.recyclerview1.setHasFixedSize(true); + binding.recyclerview1.setNestedScrollingEnabled(false); + binding.recyclerview1.setLayoutManager(linearLayoutManager); + binding.recyclerview1.setAdapter(new gd.baseadapter(getActivity(), list)); + new Thread() { + @Override + public void run() { + super.run(); + resource.recommend(list); + main.handler.post(new sx()); + } + }.start(); + + mp3list(); + binding.recyclerview2.setLayoutManager(new LinearLayoutManager(getContext())); + binding.recyclerview2.setNestedScrollingEnabled(false); + binding.recyclerview2.setAdapter(adapterMp3); + } + private class sx implements Runnable { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + binding.recyclerview1.getAdapter().notifyDataSetChanged(); + binding.recyclerview1Bar.setVisibility(View.GONE); + } + } + + public void mp3list() { + new Thread() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + super.run(); + String hq = wl.hq("/recommend/songs" + "?cookie=" + wl.Cookie); + if (hq == null) { + hq = wj.dqwb(wj.filesdri + "songs.json"); + } + try { + JSONObject jsonObject = new JSONObject(hq); + JSONObject data = jsonObject.getJSONObject("data"); + JSONArray dailySongs = data.getJSONArray("dailySongs"); + for (int i = 0; i < dailySongs.length(); i++) { + JSONObject jsonObject1 = dailySongs.getJSONObject(i); + String id = jsonObject1.getString("id"); + String name = jsonObject1.getString("name"); + JSONArray ar = jsonObject1.getJSONArray("ar"); + StringBuilder zz = new StringBuilder(); + for (int j = 0; j < ar.length(); j++) { + zz.append(ar.getJSONObject(j).getString("name")).append(' '); + } + JSONObject al = jsonObject1.getJSONObject("al"); + String picUrl = al.getString("picUrl"); + adapterMp3.list.add(new MP3(id, name, zz.toString(), picUrl)); + } + wj.xrwb(wj.filesdri + "songs.json", hq); + requireActivity().runOnUiThread(() -> { + adapterMp3.notifyDataSetChanged(); + binding.recyclerview2Bar.setVisibility(View.GONE); + }); + } catch (Exception e) { + gj.sc(e); + } + } + }.start(); + } +} diff --git a/app/src/main/java/com/muqingbfq/fragment/mp3.java b/app/src/main/java/com/muqingbfq/fragment/mp3.java new file mode 100644 index 0000000..1911b8c --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/mp3.java @@ -0,0 +1,336 @@ +package com.muqingbfq.fragment; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityOptions; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Filter; +import android.widget.Filterable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.muqingbfq.MP3; +import com.muqingbfq.R; +import com.muqingbfq.api.FileDownloader; +import com.muqingbfq.api.playlist; +import com.muqingbfq.bfq; +import com.muqingbfq.bfq_an; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.ActivityMp3Binding; +import com.muqingbfq.databinding.ListMp3Binding; +import com.muqingbfq.list.MyViewHoder; +import com.muqingbfq.main; +import com.muqingbfq.mq.FragmentActivity; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class mp3 extends FragmentActivity { + private List list = new ArrayList<>(); + private List list_ys = new ArrayList<>(); + public static Adapter adapter; + + public static void start(Activity context, String[] str, View view) { + ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(context, + view, "text"); + Intent intent = new Intent(context, mp3.class); + intent.putExtra("id", str[0]); + intent.putExtra("name", str[1]); + context.startActivity(intent, options.toBundle()); + } + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getViewBinding().getRoot()); + setToolbar(); + Intent intent = getIntent(); + binding.title.setText(intent.getStringExtra("name")); + String id = intent.getStringExtra("id"); + adapter = new Adapter(list); + binding.lb.setLayoutManager(new LinearLayoutManager(this)); + binding.lb.setAdapter(adapter); + new start(id); + binding.edittext.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + if (binding.edittext.getVisibility() == View.VISIBLE) { + adapter.getFilter().filter(charSequence); + } + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuItem itemA = menu.add("搜索"); + itemA.setTitle("搜索"); + itemA.setIcon(R.drawable.sousuo); + itemA.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + return super.onCreateOptionsMenu(menu); + } + + @Override + protected ActivityMp3Binding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityMp3Binding.inflate(layoutInflater); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + if (binding.edittext.getVisibility() == View.VISIBLE) { + binding.title.setVisibility(View.VISIBLE); + binding.edittext.setVisibility(View.GONE); + gj.ycjp(binding.edittext); + adapter.getFilter().filter(""); + } else { + ActivityCompat.finishAfterTransition(this); + } + } else if (itemId == 0) { + binding.title.setVisibility(View.GONE); + binding.edittext.setVisibility(View.VISIBLE); + gj.tcjp(binding.edittext); + } + return true; + } + + @Override + public void onBackPressed() { + if (binding.edittext.getVisibility() == View.VISIBLE) { + binding.title.setVisibility(View.VISIBLE); + binding.edittext.setVisibility(View.GONE); + gj.ycjp(binding.edittext); + adapter.getFilter().filter(""); + } else { + ActivityCompat.finishAfterTransition(this); + } + + } + + @SuppressLint("NotifyDataSetChanged") + class start extends Thread { + String id; + + public start(String id) { + binding.recyclerview1Bar.setVisibility(View.VISIBLE); + this.id = id; + list.clear(); + list_ys.clear(); + start(); + } + + @Override + public void run() { + super.run(); + switch (id) { + case "mp3_xz.json": + playlist.hq_xz(list); + break; + case "mp3_like.json": + playlist.hq_like(list); + break; + case "cd.json": + playlist.hq_cd(mp3.this, list); + break; + default: + playlist.hq(list, id); + break; + } + list_ys = list; + main.handler.post(() -> { + binding.lb.getAdapter().notifyDataSetChanged(); + binding.recyclerview1Bar.setVisibility(View.GONE); + if (list.isEmpty()) { + binding.recyclerview1Text.setVisibility(View.VISIBLE); + binding.recyclerview1Text.setOnClickListener(v -> new start(id)); + } else { + binding.recyclerview1Text.setVisibility(View.GONE); + } + }); + } + } + public static class Adapter extends RecyclerView.Adapter implements Filterable { + + private List list; + private List list_ys; + + public Adapter(List list) { + this.list = list; + list_ys = list; + } + + @NonNull + @Override + public MyViewHoder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new MyViewHoder(ListMp3Binding.bind(LayoutInflater.from(parent.getContext()). + inflate(R.layout.list_mp3, + parent, false))); + } + + @Override + public void onBindViewHolder(@NonNull MyViewHoder holder, int position) { + MP3 x = list.get(position); + holder.binding.text1.setText(String.valueOf(position + 1)); + holder.binding.name.setText(x.name); + holder.binding.zz.setText(x.zz); + if (bfqkz.xm != null && x.id.equals(bfqkz.xm.id)) { + TypedValue typedValue = new TypedValue(); + holder.itemView.getContext().getTheme().resolveAttribute( + com.google.android.material.R.attr.colorSurfaceVariant, typedValue, true); + int colorSurface = typedValue.data; + // 这里 colorSurface 就是你要找的颜色值 + holder.binding.getRoot().setCardBackgroundColor(colorSurface); + }else{ + holder.binding.getRoot().setCardBackgroundColor(ContextCompat + .getColor(holder.itemView.getContext(), android.R.color.transparent)); + } +// holder.binding.zz.setTextColor(color); + holder.itemView.setOnClickListener(view -> { + if (bfqkz.list!=list) { + bfqkz.list.clear(); + bfqkz.list.addAll(list); + } + bfq.startactivity(holder.getContext(), x); + }); + holder.itemView.setOnLongClickListener(view -> { + List stringList = new ArrayList<>(); + boolean getlike = bfq_an.getlike(x); + if (getlike) { + stringList.add("取消喜欢"); + } else { + stringList.add("喜欢歌曲"); + } + if (wj.cz(wj.mp3 + x.id)) { + stringList.add("删除下载"); + } else { + stringList.add("下载歌曲"); + } + stringList.add("复制名字"); + String[] array = stringList.toArray(new String[0]); + new MaterialAlertDialogBuilder(view.getContext()). + setItems(array, (dialog, id) -> { + switch (array[id]) { + case "下载歌曲": + new FileDownloader(view.getContext()).downloadFile(x); + break; + case "删除下载": + wj.sc(wj.mp3 + x.id); +/* if (sc&&) { + list.remove(position); + notifyItemRemoved(position); + }*/ + break; + case "喜欢歌曲": + case "取消喜欢": + try { + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + List list = gson.fromJson(wj.dqwb(wj.gd + "mp3_like.json"), type); + if (list == null) { + list = new ArrayList<>(); + } + if (list.contains(x)) + list.remove(x); + else + list.add(x); + wj.xrwb(wj.gd + "mp3_like.json", gson.toJson(list)); + } catch (Exception e) { + e.printStackTrace(); + } + break; + case "复制名字": + gj.fz(view.getContext(), x.name); + break; + + } + }).show(); + return false; + }); + } + + @Override + public int getItemCount() { + return list.size(); + } + + @Override + public Filter getFilter() { + return new Filter() { + @Override + protected FilterResults performFiltering(CharSequence charSequence) { + String charString = charSequence.toString(); + if (charString.isEmpty()) { + //没有过滤的内容,则使用源数据 + list = list_ys; + } else { + List filteredList = new ArrayList<>(); + for (int i = 0; i < list_ys.size(); i++) { + MP3 mp3 = list_ys.get(i); + if (mp3.name.contains(charString) + || mp3.zz.contains(charString)) { + filteredList.add(list_ys.get(i)); + } + } + list = filteredList; + } + + FilterResults filterResults = new FilterResults(); + filterResults.values = list; + return filterResults; + } + + @SuppressLint("NotifyDataSetChanged") + @SuppressWarnings("unchecked") + @Override + protected void publishResults(CharSequence charSequence, FilterResults filterResults) { + list = (List) filterResults.values; + notifyDataSetChanged(); + } + }; + } + } + + + public static void startactivity(Context context, String id) { + context.startActivity(new Intent(context, mp3.class).putExtra("id", id)); + } + + @Override + public void finish() { + super.finish(); + adapter = null; + } +} diff --git a/app/src/main/java/com/muqingbfq/fragment/search.java b/app/src/main/java/com/muqingbfq/fragment/search.java new file mode 100644 index 0000000..85edfda --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/search.java @@ -0,0 +1,230 @@ +package com.muqingbfq.fragment; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import com.google.android.material.tabs.TabLayoutMediator; +import com.muqingbfq.MP3; +import com.muqingbfq.XM; +import com.muqingbfq.databinding.FragmentSearchBinding; +import com.muqingbfq.databinding.RecyclerVBinding; +import com.muqingbfq.main; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wl; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +public class search extends Fragment { + public FragmentSearchBinding binding; + public String string; + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = FragmentSearchBinding.inflate(inflater, container, false); + binding.viewPager.setSaveEnabled(false); + adapter = new FragmentStateAdapter(this) { + @NonNull + @Override + public Fragment createFragment(int position) { + return fragments.get(position); + } + + @Override + public int getItemCount() { + return fragments.size(); + } + }; + return binding.getRoot(); + } + + public static class mp3 extends Fragment { + public static mp3 newInstance(String string) { + mp3 fragment = new mp3(); + Bundle args = new Bundle(); + args.putString("string", string); + fragment.setArguments(args); + return fragment; + } + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + List list = new ArrayList<>(); + String string = getArguments().getString("string"); + RecyclerVBinding binding = RecyclerVBinding.inflate(inflater, container, false); + binding.recycleview.setLayoutManager(new LinearLayoutManager(getContext())); + binding.recycleview.setAdapter(new com.muqingbfq.fragment.mp3.Adapter(list)); + list.clear(); + binding.recyclerviewBar.setVisibility(View.VISIBLE); + binding.recyclerviewText.setVisibility(View.GONE); + new Thread() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + super.run(); + mp3(list, string); + main.handler.post(() -> { + binding.recyclerviewBar.setVisibility(View.GONE); + if (list.isEmpty()) { + binding.recyclerviewText.setVisibility(View.VISIBLE); + } else { + binding.recyclerviewText.setVisibility(View.GONE); + } + binding.recycleview.getAdapter().notifyDataSetChanged(); + }); + } + }.start(); + return binding.getRoot(); + } + } + public static class gd extends Fragment { + public static gd newInstance(String string) { + gd fragment = new gd(); + Bundle args = new Bundle(); + args.putString("string", string); + fragment.setArguments(args); + return fragment; + } + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + List list = new ArrayList<>(); + String string = getArguments().getString("string"); + RecyclerVBinding binding = RecyclerVBinding.inflate(inflater, container, false); + binding.recycleview.setLayoutManager(new LinearLayoutManager(getContext())); + binding.recycleview.setAdapter(new com.muqingbfq.fragment.gd.baseadapter(getActivity(), + list, true)); + list.clear(); + binding.recyclerviewBar.setVisibility(View.VISIBLE); + binding.recyclerviewText.setVisibility(View.GONE); + new Thread() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + super.run(); + gd(list, string); + main.handler.post(() -> { + binding.recyclerviewBar.setVisibility(View.GONE); + if (list.isEmpty()) { + binding.recyclerviewText.setVisibility(View.VISIBLE); + } else { + binding.recyclerviewText.setVisibility(View.GONE); + } + binding.recycleview.getAdapter().notifyDataSetChanged(); + }); + } + }.start(); + return binding.getRoot(); + } + } + List fragments=new ArrayList<>(); + private FragmentStateAdapter adapter; + @SuppressLint("NotifyDataSetChanged") + public void sx(String string) { + this.string = string; + fragments.clear(); + fragments.add(mp3.newInstance(string)); + fragments.add(gd.newInstance(string)); + binding.viewPager.setAdapter(adapter); + adapter.notifyDataSetChanged(); + String[] strtab = new String[]{"歌曲", "歌单"}; + //将tabbView绑定到tab + new TabLayoutMediator(binding.tablayout, binding.viewPager, (tab, position) -> + tab.setText(strtab[position])).attach(); + } + + private static void mp3(List list, String str) { + try { + Long.parseLong(str); + com.muqingbfq.api.playlist.hq(list, str); + return; + } catch (NumberFormatException e) { + gj.sc(e); + } + String hq = wl.hq("/search?keywords=" + str + "&type=1"); + try { + JSONArray jsonArray = new JSONObject(hq).getJSONObject("result") + .getJSONArray("songs"); + int length = jsonArray.length(); + for (int i = 0; i < length; i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String id = jsonObject.getString("id"); + String name = jsonObject.getString("name"); + JSONArray artists = jsonObject.getJSONArray("artists"); + int length1 = artists.length(); + StringBuilder zz = null; + for (int j = 0; j < length1; j++) { + JSONObject josn = artists.getJSONObject(j); + String name_zz = josn.getString("name"); + if (zz == null) { + zz = new StringBuilder(name_zz); + } else { + zz.append("/").append(name_zz); + } + } + list.add(new MP3(id, name, zz.toString(), "")); + } + } catch (Exception e) { + gj.sc(e); + } + } + + private static void gd(List list, String str) { + try { + Long.parseLong(str); + String hq = wl.hq("/playlist/detail?id=" + str); + JSONObject js = new JSONObject(hq).getJSONObject("playlist"); + String id = js.getString("id"); + String name = js.getString("name"); + String coverImgUrl = js.getString("coverImgUrl"); +// gj.sc(name); + list.add(new XM(id, name, coverImgUrl)); + return; + } catch (Exception e) { + gj.sc(e); + } + try { + String hq = wl.hq("/search?keywords=" + str + "&type=1000"); + JSONArray jsonArray = new JSONObject(hq).getJSONObject("result") + .getJSONArray("playlists"); + int length = jsonArray.length(); + for (int i = 0; i < length; i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String id = jsonObject.getString("id"); + + String name = jsonObject.getString("name"); + String coverImgUrl = jsonObject.getString("coverImgUrl"); + + long playCount = jsonObject.getLong("playCount"); + String formattedNumber = String.valueOf(playCount); + if (playCount > 9999) { + DecimalFormat df = new DecimalFormat("#,###.0万"); + formattedNumber = df.format(playCount / 10000); + } + String s = jsonObject.getInt("trackCount") + "首," + + "by " + jsonObject.getJSONObject("creator").getString("nickname") + + ",播放" + + formattedNumber + "次"; + list.add(new XM(id, name, s, coverImgUrl)); + } + } catch (Exception e) { + gj.sc(e); + } + } +} diff --git a/app/src/main/java/com/muqingbfq/fragment/sz.java b/app/src/main/java/com/muqingbfq/fragment/sz.java new file mode 100644 index 0000000..825d592 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/sz.java @@ -0,0 +1,70 @@ +package com.muqingbfq.fragment; + +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.widget.Toast; + +import com.muqingbfq.R; +import com.muqingbfq.activity_about_software; +import com.muqingbfq.clean.fragment_clean; +import com.muqingbfq.home; +import com.muqingbfq.main; +import com.muqingbfq.mq.gj; + +import java.util.List; + +public class sz { + @SuppressLint("NonConstantResourceId") + public static void switch_sz(Context context, int id) { + if (id == R.id.a) { + gj.llq(context, "https://rust.coldmint.top/ftp/muqing/"); + } else if (id == R.id.b) { + context.startActivity(new Intent(context, com.muqingbfq.sz.class)); +// 设置中心 + } else if (id == R.id.c) { + context.startActivity(new Intent(context, fragment_clean.class)); +// 储存清理 + } else if (id == R.id.d) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse("mqqapi://card/show_pslcard?card_type=group&uin=" + + 674891685)); + context.startActivity(intent); + } catch (Exception e) { + Toast.makeText(context, "无法打开 QQ", Toast.LENGTH_SHORT).show(); + } + // 如果没有安装 QQ 客户端或无法打开 QQ,您可以在此处理异常 +// 官方聊群 + } else if (id == R.id.e) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse( + "mqqwpa://im/chat?chat_type=wpa&uin=" + "1966944300")); + context.startActivity(intent); + } catch (Exception e) { + // 如果没有安装 QQ 或无法跳转,则会抛出异常 + Toast.makeText(context, "无法打开 QQ", Toast.LENGTH_SHORT).show(); + } +// 联系作者 + } else if (id == R.id.f) { + context.startActivity(new Intent(context, activity_about_software.class)); +// 关于软件 + } else if (id == R.id.g) { +// 关闭软件 + ActivityManager mActivityManager = (ActivityManager) + main.application.getSystemService(Context.ACTIVITY_SERVICE); + List mList = mActivityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo runningAppProcessInfo : mList) + { + if (runningAppProcessInfo.pid != android.os.Process.myPid()) + { + android.os.Process.killProcess(runningAppProcessInfo.pid); + } + } + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(0); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/fragment/wode.java b/app/src/main/java/com/muqingbfq/fragment/wode.java new file mode 100644 index 0000000..0a55897 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/wode.java @@ -0,0 +1,450 @@ +package com.muqingbfq.fragment; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.gson.Gson; +import com.muqingbfq.R; +import com.muqingbfq.XM; +import com.muqingbfq.api.playlist; +import com.muqingbfq.api.resource; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.FragmentWdBinding; +import com.muqingbfq.databinding.ListGdBBinding; +import com.muqingbfq.login.user_logs; +import com.muqingbfq.login.visitor; +import com.muqingbfq.main; +import com.muqingbfq.mq.EditViewDialog; +import com.muqingbfq.mq.Fragment; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; +import com.muqingbfq.mq.wl; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class wode extends Fragment { + public TextView name, jieshao; + public ImageView imageView; + private final Object[][] lista = { + {R.drawable.mdimusicbox, "最近播放", "mp3_hc.json"}, + {R.drawable.download, "下载音乐", "mp3_xz.json"}, + {R.drawable.mdialbum, "喜欢音乐", "mp3_like.json"}, + {R.drawable.filesearc, "本地搜索", "cd.json"}, + {R.drawable.api, "更换接口", "API"}, + {R.drawable.gd, "导入歌单", "gd"}, + {R.drawable.paihangbang, "排行榜", "排行榜"}, + {R.drawable.ic_launcher_foreground, "开发中", ""} + }; + + @SuppressLint("StaticFieldLeak") + public static baseadapter adaper; + + @Override + protected FragmentWdBinding inflateViewBinding(LayoutInflater inflater, ViewGroup container) { + return FragmentWdBinding.inflate(inflater, container, false); + } + + @Override + public void setUI(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + name = binding.text1; + jieshao = binding.text2; + imageView = binding.imageView; + binding.cardview.setOnClickListener(new dl()); + + binding.recyclerview1.setNestedScrollingEnabled(false); + binding.recyclerview1.setLayoutManager(new GridLayoutManager(getContext(), 4)); + binding.recyclerview1.setFocusable(false); + binding.recyclerview1.setAdapter(new RecyclerView.Adapter() { + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View inflate = View.inflate(getContext(), R.layout.view_button, null); + return new VH(inflate); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + String s = lista[position][1].toString(); + holder.textView.setText(s); + holder.imageView.setImageResource((Integer) lista[position][0]); + String data = lista[position][2].toString(); + holder.itemView.setOnClickListener(view -> { + switch (data) { + case "cd.json": + case "mp3_hc.json": + case "mp3_xz.json": + case "mp3_like.json": + Intent a = new Intent(getContext(), com.muqingbfq.fragment.mp3.class); + a.putExtra("id", data); + a.putExtra("name", s); + getContext().startActivity(a); + break; + case "排行榜": + gd.start(getActivity(), new String[]{data, s}, holder.textView); + break; + case "API": + EditViewDialog editViewDialog = new EditViewDialog(getContext(), "更换接口API") + .setMessage("当前接口:\n" + main.api); + editViewDialog.setPositive(view1 -> { + String str = editViewDialog.getEditText(); + boolean http = str.startsWith("http"); + if (str.isEmpty() || !http) { + gj.ts(getContext(), "请输入正确的api"); + } else { + gj.ts(getContext(), "更换成功"); + main.api = str; + wj.xrwb(wj.filesdri + "API.mq", main.api); + editViewDialog.dismiss(); + } + }).show(); + break; + case "gd": + EditViewDialog editViewDialog1 = new EditViewDialog(getContext(), + "导入歌单") + .setMessage("请用网易云https链接来进行导入或者歌单id"); + editViewDialog1.setPositive(view1 -> { + String str = editViewDialog1.getEditText(); + // 使用正则表达式提取链接 + Pattern pattern = Pattern.compile("https?://[\\w./?=&]+"); + Matcher matcher = pattern.matcher(str); + if (matcher.find()) + str = matcher.group(); + if (!str.isEmpty()) { + // 使用截取方法获取歌单 ID + str = str.substring(str.indexOf("id=") + 3, str.indexOf("&")); + } + String finalStr = str; + gj.ts(getContext(), "导入中"); + new Thread() { + @Override + public void run() { + super.run(); + String hq = playlist.gethq(finalStr); + if (hq != null) { + try { + XM fh = resource.Playlist_content(finalStr); + JSONObject json = new JSONObject(hq); + json.put("name", fh.name); + json.put("picUrl", fh.picurl); + json.put("message", fh.message); +// json.put(fh.id, json); + wj.xrwb(wj.gd + finalStr, json.toString()); + addlist(fh); + } catch (JSONException e) { + gj.sc("list gd onclick thear " + e); + } + } + } + }.start(); + editViewDialog1.dismiss(); + }).show(); + break; + } + }); + } + + @Override + public int getItemCount() { + return lista.length; + } + }); + + binding.recyclerview2.setNestedScrollingEnabled(false); + binding.recyclerview2.setLayoutManager(new LinearLayoutManager(getContext())); + adaper = new baseadapter(); + binding.recyclerview2.setAdapter(adaper); + sx(); + new threadLogin(main.sp.getString("Cookie", "")).start(); + } + + ActivityResultLauncher dlintent = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + // 处理返回结果 + Intent data = result.getData(); + boolean bool = data.getBooleanExtra("bool", false); + if (bool) { + new threadLogin(main.sp.getString("Cookie", "")); + } + // ... + } + }); + + class dl implements View.OnClickListener { + @Override + public void onClick(View v) { + File file = new File(wj.filesdri, "user.mq"); + if (file.exists()) { + String[] a = new String[]{"退出登录"}; + new MaterialAlertDialogBuilder(getContext()) + .setItems(a, (dialogInterface, i) -> { + boolean delete = file.delete(); + if (delete) { + binding.text1.setText(getString(R.string.app_name)); + binding.text2.setText(getString(R.string.app_name)); + imageView.setImageResource(R.drawable.ic_launcher_foreground); + new visitor(); + new com.muqingbfq.login.user_message(); + } + }).show(); + } else { + dlintent.launch(new Intent(getContext(), user_logs.class)); + } + } + } + + + class VH extends RecyclerView.ViewHolder { + public ImageView imageView; + public TextView textView; + + public VH(@NonNull View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.image); + textView = itemView.findViewById(R.id.text1); + } + } + + @SuppressLint("NotifyDataSetChanged") + public void sx() { + adaper.list.clear(); + new Thread() { + @Override + public void run() { + super.run(); + try { + File file = new File(wj.filesdri + "gd"); + File[] files = file.listFiles(); + for (File a : files) { + String id = a.getName(); + if (id.endsWith(".json")) { + continue; + } + String dqwb = wj.dqwb(a.toString()); + JSONObject jsonObject = new JSONObject(dqwb); + String name = jsonObject.getString("name"); + String picUrl = jsonObject.getString("picUrl"); + String message = jsonObject.getString("message"); + adaper.list.add(new XM(id, name, message, picUrl)); + } + main.handler.post(() -> { + adaper.notifyDataSetChanged(); + if (adaper.list.isEmpty()) { + binding.recyclerview2Text.setVisibility(View.VISIBLE); + } else { + binding.recyclerview2Text.setVisibility(View.GONE); + } + }); + } catch (Exception e) { + gj.sc(e); + } + } + }.start(); + } + + public static void addlist(XM xm) { + if (adaper != null) { + adaper.list.add(xm); + main.handler.post(() -> adaper.notifyItemChanged(adaper.list.size() - 1)); + } + } + + public static void removelist(XM xm) { + if (adaper != null) { + int i = adaper.list.indexOf(xm); + adaper.list.remove(xm); + adaper.notifyItemRemoved(i); + } + } + + //登陆 获取用户信息 头像 昵称 签名 + class threadLogin extends Thread { + String cookie; + + public threadLogin(String cookie) { + this.cookie = cookie; + + } + + public void run() { + String hq = wl.hq("/user/account?cookie=" + cookie); + if (hq != null) { + try { + JSONObject jsonObject = new JSONObject(hq); + int code = jsonObject.getInt("code"); + if (code == 200) { + JSONObject profile = jsonObject.getJSONObject("profile"); + String nickname = profile.getString("nickname"); + String avatarUrl = profile.getString("avatarUrl"); + String signature = profile.getString("signature"); + requireActivity().runOnUiThread(() -> { + binding.text1.setText(nickname); + binding.text2.setText(signature); + Glide.with(getContext()) + .load(avatarUrl) + .error(R.drawable.ic_launcher_foreground) + .into(binding.imageView); + }); + wj.xrwb(wj.filesdri + "user.mq", new Gson().toJson(new user_logs.USER(nickname, signature, avatarUrl))); + } + } catch (Exception e) { + gj.sc(e); + } + } else { + if (!wj.cz(wj.filesdri + "user.mq")) { + return; + } + String dqwb = wj.dqwb(wj.filesdri + "user.mq"); + user_logs.USER user = new Gson().fromJson(dqwb, user_logs.USER.class); + requireActivity().runOnUiThread(() -> { + binding.text1.setText(user.name); + binding.text2.setText(user.qianming); + Glide.with(getContext()) + .load(user.picUrl) + .error(R.drawable.ic_launcher_foreground) + .into(binding.imageView); + }); + } + } + } + + class baseadapter extends RecyclerView.Adapter { + public List list = new ArrayList<>(); + + @NonNull + @Override + public gd.VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new gd.VH(ListGdBBinding.bind(LayoutInflater.from(getContext()) + .inflate(R.layout.list_gd_b, parent, false))); + } + + @Override + public void onBindViewHolder(@NonNull gd.VH holder, int position) { + XM xm = list.get(position); + holder.itemView.setOnClickListener(v -> mp3.start(getActivity(), new String[]{xm.id, xm.name}, v)); + holder.itemView.setOnLongClickListener(v -> { + String[] stringArray = {"更新歌单", "删除歌单"}; + new MaterialAlertDialogBuilder(getContext()). + setItems(stringArray, (dialog, id) -> { + switch (stringArray[id]) { + case "更新歌单": + new Thread() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + super.run(); + String hq = playlist.gethq(xm.id); + if (hq != null) { + try { + XM fh = resource.Playlist_content(xm.id); + JSONObject json = new JSONObject(hq); + json.put("name", fh.name); + json.put("picUrl", fh.picurl); + json.put("message", fh.message); + xm.name = fh.name; + xm.picurl = fh.picurl; + xm.message = fh.message; +// json.put(fh.id, json); + wj.xrwb(wj.gd + xm.id, json.toString()); + requireActivity().runOnUiThread(() -> notifyItemChanged(holder.getAdapterPosition())); + } catch (JSONException e) { + gj.sc("list gd onclick thear " + e); + } + } + } + }.start(); + break; + case "删除歌单": +// 删除项目 + try { + wj.sc(wj.gd + xm.id); + list.remove(xm); + notifyItemRemoved(position); + } catch (Exception e) { + gj.sc(e); + } + break; + } + // 在这里处理菜单项的点击事件 + }).show(); + return false; + }); + + holder.bindingB.text2.setText(xm.message); + holder.bindingB.text1.setText(xm.name); + holder.bindingB.kg.setOnClickListener(view1 -> { + ImageView tx = (ImageView) view1; + new Thread() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + super.run(); + boolean an = playlist.hq(bfqkz.list, xm.id); + if (bfqkz.ms == 2) { + Collections.shuffle(bfqkz.list); + } + main.handler.post(() -> { + if (an) { + com.muqingbfq.bfq_an.xyq(); + tx.setImageResource(R.drawable.bf); + com.muqingbfq.fragment.gd.gdid = xm.id; + } + notifyDataSetChanged(); + }); + } + }.start(); + }); + Drawable color_kg = ContextCompat.getDrawable(getContext(), R.drawable.zt); + if (xm.id.equals(com.muqingbfq.fragment.gd.gdid)) { + color_kg = ContextCompat.getDrawable(getContext(), R.drawable.bf); + } + holder.bindingB.kg.setImageDrawable(color_kg); + Glide.with(holder.itemView.getContext()) + .load(xm.picurl) + .apply(new RequestOptions() + .placeholder(R.drawable.ic_launcher_foreground) + .error(R.drawable.ic_launcher_foreground)) + .into(holder.bindingB.image); + } + + @Override + public int getItemCount() { + if (list.isEmpty()) { + binding.recyclerview2Text.setVisibility(View.VISIBLE); + } else { + binding.recyclerview2Text.setVisibility(View.GONE); + } + return list.size(); + } + } +} diff --git a/app/src/main/java/com/muqingbfq/home.java b/app/src/main/java/com/muqingbfq/home.java new file mode 100644 index 0000000..83395f0 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/home.java @@ -0,0 +1,239 @@ +package com.muqingbfq; + +import android.annotation.SuppressLint; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.support.v4.media.MediaBrowserCompat; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.fragment.app.Fragment; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; + +import com.jaeger.library.StatusBarUtil; +import com.muqingbfq.databinding.ActivityHomeBinding; +import com.muqingbfq.fragment.gd_adapter; +import com.muqingbfq.fragment.wode; +import com.muqingbfq.mq.AppCompatActivity; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wl; + +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class home extends AppCompatActivity { + public MediaBrowserCompat mBrowser; + + @Override + protected ActivityHomeBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityHomeBinding.inflate(layoutInflater); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(R.style.Theme_muqing); + super.onCreate(savedInstanceState); + wl.Cookie = main.sp.getString("Cookie", ""); + if (wl.Cookie.isEmpty()) { + new Thread() { + @Override + public void run() { + super.run(); + String hq = wl.hq("/register/anonimous"); + try { + JSONObject jsonObject = new JSONObject(hq); + wl.setcookie(jsonObject.getString("cookie")); + home.this.runOnUiThread(() -> UI()); + } catch (Exception e) { + com.muqingbfq.mq.gj.sc(e); + } + } + }.start(); + } else { + UI(); + } + } + + @Override + protected void onResume() { + super.onResume(); + } + + public static ComponentName componentName; + + + public void toolbar() { +// binding.bar.setupWithDrawer + setSupportActionBar(binding.toolbar); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( + this, binding.chct, binding.toolbar, R.string.app_name, R.string.app_name); + binding.chct.addDrawerListener(toggle); + toggle.syncState(); + binding.toolbar.setOnClickListener(v -> activity_search.start(home.this, v)); + } + + public void UI() { + StatusBarUtil.setTransparent(home.this); + setContentView(); + toolbar(); + //初始化侧滑 + binding.chb.setNavigationItemSelectedListener(item -> { + com.muqingbfq.fragment.sz.switch_sz(home.this, item.getItemId()); + return false; + }); + //初始化播放器组件 + // 启动Service + if (componentName == null) { + componentName = new ComponentName(getApplicationContext(), bfqkz.class); + mBrowser = new MediaBrowserCompat( + getApplicationContext(), componentName + ,//绑定服务端 + browserConnectionCallback,//设置连接回调 + null + ); + mBrowser.connect(); + } + //检测更新 + new gj.jianchagengxin(home.this); + List list = new ArrayList<>(); + list.add(new gd_adapter()); + list.add(new wode()); + binding.viewPager.setAdapter(new FragmentStateAdapter(this) { + @NonNull + @Override + public Fragment createFragment(int position) { + return list.get(position); + } + + @Override + public int getItemCount() { + return list.size(); + } + }); + binding.viewPager.setSaveEnabled(false); +// 将 ViewPager2 绑定到 TabLayou + binding.tablayout.setOnItemSelectedListener(item -> { + int itemId = item.getItemId(); + if (itemId == R.id.a) { + binding.viewPager.setCurrentItem(0); + } else if (itemId == R.id.c) { + binding.viewPager.setCurrentItem(1); + } + return true; + }); + binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + switch (position) { + case 0: + binding.tablayout.setSelectedItemId(R.id.a); + break; + case 1: + binding.tablayout.setSelectedItemId(R.id.c); + break; + } + } + }); + } + + @Override + protected void onPause() { + super.onPause(); + //在销毁 Activity 之前,系统会先调用 onDestroy()。系统调用此回调的原因如下: + // 保存列表数据 + SharedPreferences sharedPreferences = getSharedPreferences("list", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + String jsonList = new com.google.gson.Gson().toJson(bfqkz.list); + editor.putString("listData", jsonList); + editor.apply(); + } + + @Override + public void onBackPressed() { + moveTaskToBack(true); + } + + /** + * 连接状态的回调接口,连接成功时会调用onConnected()方法 + */ + private final MediaBrowserCompat.ConnectionCallback browserConnectionCallback = + new MediaBrowserCompat.ConnectionCallback() { + @Override + public void onConnected() { + //必须在确保连接成功的前提下执行订阅的操作 + if (mBrowser.isConnected()) { + //mediaId即为MediaBrowserService.onGetRoot的返回值 + //若Service允许客户端连接,则返回结果不为null,其值为数据内容层次结构的根ID + //若拒绝连接,则返回null + String mediaId = mBrowser.getRoot(); + + //Browser通过订阅的方式向Service请求数据,发起订阅请求需要两个参数,其一为mediaId + //而如果该mediaId已经被其他Browser实例订阅,则需要在订阅之前取消mediaId的订阅者 + //虽然订阅一个 已被订阅的mediaId 时会取代原Browser的订阅回调,但却无法触发onChildrenLoaded回调 + + //ps:虽然基本的概念是这样的,但是Google在官方demo中有这么一段注释... + // This is temporary: A bug is being fixed that will make subscribe + // consistently call onChildrenLoaded initially, no matter if it is replacing an existing + // subscriber or not. Currently this only happens if the mediaID has no previous + // subscriber or if the media content changes on the service side, so we need to + // unsubscribe first. + //大概的意思就是现在这里还有BUG,即只要发送订阅请求就会触发onChildrenLoaded回调 + //所以无论怎样我们发起订阅请求之前都需要先取消订阅 + mBrowser.unsubscribe(mediaId); + //之前说到订阅的方法还需要一个参数,即设置订阅回调SubscriptionCallback + //当Service获取数据后会将数据发送回来,此时会触发SubscriptionCallback.onChildrenLoaded回调 + mBrowser.subscribe(mediaId, browserSubscriptionCallback); + } + gj.sc("连接成功"); + } + + @Override + public void onConnectionFailed() { + gj.sc("连接失败!"); + } + }; + /** + * 向媒体服务器(MediaBrowserService)发起数据订阅请求的回调接口 + */ + private final MediaBrowserCompat.SubscriptionCallback browserSubscriptionCallback = + new MediaBrowserCompat.SubscriptionCallback() { + @Override + public void onChildrenLoaded(@NonNull String parentId, + @NonNull List children) { + gj.sc("onChildrenLoaded------"); + } + }; + + @Override + public void finish() { + super.finish(); + // 断开连接并释放资源 + if (mBrowser != null && mBrowser.isConnected()) { + mBrowser.disconnect(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.home, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_search) { + startActivity(new Intent(this, activity_search.class)); + } + return super.onOptionsItemSelected(item); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/list/MyViewHoder.java b/app/src/main/java/com/muqingbfq/list/MyViewHoder.java new file mode 100644 index 0000000..0f082e2 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/list/MyViewHoder.java @@ -0,0 +1,28 @@ +package com.muqingbfq.list; + +import android.content.Context; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.muqingbfq.R; +import com.muqingbfq.databinding.ListMp3ABinding; +import com.muqingbfq.databinding.ListMp3Binding; + +public class MyViewHoder extends RecyclerView.ViewHolder { + public ListMp3Binding binding; + public ListMp3ABinding bindingA; + public MyViewHoder(@NonNull ListMp3Binding itemView) { + super(itemView.getRoot()); + binding = itemView; + } + public MyViewHoder(@NonNull ListMp3ABinding itemView) { + super(itemView.getRoot()); + bindingA = itemView; + } + public Context getContext() { + return itemView.getContext(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/list/file_list.java b/app/src/main/java/com/muqingbfq/list/file_list.java new file mode 100644 index 0000000..dcfd3e0 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/list/file_list.java @@ -0,0 +1,5 @@ +package com.muqingbfq.list; + +public class file_list { + +} diff --git a/app/src/main/java/com/muqingbfq/list/yylb.java b/app/src/main/java/com/muqingbfq/list/yylb.java new file mode 100644 index 0000000..d33d46e --- /dev/null +++ b/app/src/main/java/com/muqingbfq/list/yylb.java @@ -0,0 +1,4 @@ +package com.muqingbfq.list; + +public class yylb { +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/login/user_logs.java b/app/src/main/java/com/muqingbfq/login/user_logs.java new file mode 100644 index 0000000..5faece1 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/login/user_logs.java @@ -0,0 +1,218 @@ +package com.muqingbfq.login; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Base64; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.NonNull; + +import com.muqingbfq.databinding.ActivityUserLogsBinding; +import com.muqingbfq.main; +import com.muqingbfq.mq.AppCompatActivity; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wl; + +import org.json.JSONObject; + +import java.util.Objects; + + +public class user_logs extends AppCompatActivity { + @Override + protected ActivityUserLogsBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityUserLogsBinding.inflate(layoutInflater); + } + + public static class USER { + public String name, qianming; + public Object picUrl; + + public USER(String user, String qianming, Object picUrl) { + this.name = user; + this.qianming = qianming; + this.picUrl = picUrl; + } + } + + erweima thread; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(); + setSupportActionBar(binding.toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + binding.login.setOnClickListener(view -> new CloudUser()); + binding.button1.setOnClickListener(view -> { + if (binding.layout1.getVisibility() == View.VISIBLE) { + binding.layout1.setVisibility(View.GONE); + binding.layout2.setVisibility(View.VISIBLE); + binding.button1.setText("账号"); + thread = new erweima(); + thread.start(); + } else { + thread.interrupt(); + binding.button1.setText("二维码"); + binding.layout1.setVisibility(View.VISIBLE); + binding.layout2.setVisibility(View.GONE); + } + }); + } + //some statement + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(false); + } + return super.onOptionsItemSelected(item); + } + + public void finish(boolean aBoolean) { + Intent resultIntent = new Intent(); + resultIntent.putExtra("bool", aBoolean); + setResult(RESULT_OK, resultIntent); + super.finish(); + } + + @Override + public void onBackPressed() { + finish(false); + } + + public static Bitmap stringToBitmap(String string) { + Bitmap bitmap = null; + try { + byte[] bitmapArray = Base64.decode(string.split(",")[1], Base64.DEFAULT); + bitmap = BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.length); + } catch (Exception e) { + e.printStackTrace(); + } + return bitmap; + } + + public String account, password; + + class CloudUser extends Thread { + public CloudUser() { + InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + View v = getWindow().peekDecorView(); + if (null != v) { + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + user_logs.this.account = binding.editUser.getText().toString(); + gj.xcts(user_logs.this, "设置成功"); + wl.setcookie(account); + finish(); +// start(); + } + + @Override + public void run() { + super.run(); + try { + String hq = wl.hq("/login/cellphone?phone=" + account + "&password=" + password); + if (TextUtils.isEmpty(hq)) { + return; + } + JSONObject jsonObject = new JSONObject(hq); + int code = jsonObject.getInt("code"); + if (code == 200) { + JSONObject data = jsonObject.getJSONObject("profile"); + String nickname = data.getString("nickname");//用户名 + String avatarUrl = data.getString("avatarUrl");//用户头像 + String signature = data.getString("signature");//用户签名 + String cookie = jsonObject.getString("cookie"); + new user_message(nickname, signature, avatarUrl); + user_logs.this.finish(true); + } else if (code == 502) { + gj.xcts(user_logs.this, jsonObject.getString("message")); + } else { + gj.xcts(user_logs.this, "找不到此账号"); + } + } catch (Exception e) { + gj.sc(e); + } + } + } + + class erweima extends Thread { + int code = 800; + String unikey, qrimg, hq; + private long time = 0; + + public erweima() { + binding.text1.setText("请使用网易云音乐扫码"); + } + + @Override + public void run() { + super.run(); + while (code != 0 && !Thread.currentThread().isInterrupted()) { + gj.sc(code); + try { + hq = wl.hq("/login/qr/check?key=" + unikey + Time()); + if (hq != null) { + JSONObject json = new JSONObject(hq); + code = json.getInt("code"); + switch (code) { + case 800: + case 400: + setwb("二维码过期"); + hqkey(); + break; + case 801: + setwb("等待扫码"); + break; + case 802: + setwb("等待确认"); + break; + case 803: + setwb("登录成功"); + wl.setcookie(json.getString("cookie")); + code = 0; + user_logs.this.finish(true); + break; + default: + code = 0; + // 默认情况下的操作 + break; + } + } + sleep(1000); + } catch (Exception e) { + Thread.currentThread().interrupt(); + gj.sc(e); + } + } + } + + private void hqkey() throws Exception { + unikey = new JSONObject(Objects.requireNonNull(wl.hq("/login/qr/key"))). + getJSONObject("data").getString("unikey"); + JSONObject jsonObject = new JSONObject(Objects.requireNonNull(wl.hq("/login/qr/create?key=" + + unikey + + "&qrimg=base64"))); + qrimg = jsonObject.getJSONObject("data").getString("qrimg"); + main.handler.post(() -> binding.image.setImageBitmap(user_logs.stringToBitmap(qrimg))); + } + + private String Time() { + if (time < System.currentTimeMillis() - 1000) { + time = System.currentTimeMillis(); + } + return "×tamp" + time; + } + + private void setwb(String wb) { + main.handler.post(() -> binding.text1.setText(wb)); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/login/user_message.java b/app/src/main/java/com/muqingbfq/login/user_message.java new file mode 100644 index 0000000..c327935 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/login/user_message.java @@ -0,0 +1,71 @@ +package com.muqingbfq.login; + +import com.google.gson.Gson; +import com.muqingbfq.R; +import com.muqingbfq.main; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; + +public class user_message{ + public String name,qiangming, picurl; + + public user_message() { + wj.sc(wj.filesdri + "user.mq"); + } + public user_message(String nickname, String signature, String avatarUrl) { + name = nickname; + qiangming = signature; + picurl = avatarUrl; + String s = new Gson().toJson(new user_logs.USER(name, qiangming, picurl)); + wj.xrwb(wj.filesdri + "user.mq", s); + } + + + public string get() throws Exception { +/* JSONObject post = wl.jsonpost("/php/user.php?action=getSpaceInfo", + new String[]{ + "account" + }, + new String[]{ + main.account + }); + gj.sc(post); + if (!TextUtils.isEmpty(post.toString()) && + post.getInt("code") == 0) { + JSONObject data = post.getJSONObject("data"); + String headIcon = data.getString("headIcon");//头像 + String account = data.getString("account");//账号 + String userName = data.getString("userName");//名称 + String introduce = data.getString("introduce");//签名 + String cover = data.getString("cover");//背景 + if (headIcon.startsWith("..")) { + headIcon = "https://rust.coldmint.top" + headIcon.substring(2); + } + if (cover.startsWith("..")) { + cover = "https://rust.coldmint.top" + cover.substring(2); + } + String gender = data.getString("gender");*/ + return new string(new String[]{ + name, qiangming, picurl + }); + } + + public static class string { + private final String[] strings; + public string(String[] strings) { + this.strings = strings; + } + + public String picurl() { + return strings[2]; + } + + public String qianming() { + return strings[1]; + } + + public String userName() { + return strings[0]; + } + } +} diff --git a/app/src/main/java/com/muqingbfq/login/visitor.java b/app/src/main/java/com/muqingbfq/login/visitor.java new file mode 100644 index 0000000..06b2510 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/login/visitor.java @@ -0,0 +1,29 @@ +package com.muqingbfq.login; + +import android.content.Intent; + +import androidx.appcompat.app.AppCompatActivity; + +import com.muqingbfq.mq.wj; +import com.muqingbfq.mq.wl; +import com.muqingbfq.yc; + +import org.json.JSONException; +import org.json.JSONObject; + +public class visitor extends Thread { + public visitor() { + start(); + } + @Override + public void run() { + super.run(); + String hq = wl.hq("/register/anonimous"); + try { + JSONObject jsonObject = new JSONObject(hq); + wl.setcookie(jsonObject.getString("cookie")); + } catch (Exception e) { + com.muqingbfq.mq.gj.sc(e); + } + } +} diff --git a/app/src/main/java/com/muqingbfq/main.java b/app/src/main/java/com/muqingbfq/main.java new file mode 100644 index 0000000..0cff804 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/main.java @@ -0,0 +1,169 @@ +package com.muqingbfq; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.text.TextUtils; +import android.view.LayoutInflater; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatDelegate; + +import com.google.gson.reflect.TypeToken; +import com.muqingbfq.login.visitor; +import com.muqingbfq.mq.FloatingLyricsService; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; +import com.muqingbfq.mq.wl; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.List; + +public class main extends Application { + public static Application application; + public static Handler handler = new Handler(Looper.getMainLooper()); + public static String api = "https://api.csm.sayqz.com"; + public static String http = "https://www.muqingkaifazhe.top/muqingbfq.php"; + public static SharedPreferences sp; + public static SharedPreferences.Editor edit; + private int count = 0; + @Override + public void onCreate() { + super.onCreate(); + if (wj.filesdri == null) { + new wj(this); + } + File file = new File(wj.filesdri + "API.mq"); + if (file.exists()&&file.isFile()) { + String dqwb = wj.dqwb(file.toString()); + if (!TextUtils.isEmpty(dqwb) && dqwb.startsWith("http")) { + api = dqwb; + } else { + file.delete(); + } + } else { + wj.xrwb(file.toString(), main.api); + } + application = this; + sp = getSharedPreferences("Set_up", MODE_PRIVATE); + edit = sp.edit(); + boolean bj = false; + try { + com.muqingbfq.bfqkz.ms = sp.getInt("ms", 1); + } catch (Exception e) { + edit.putInt("ms", 1); + bj = true; + com.muqingbfq.bfqkz.ms = 1; + } + try { + wl.Cookie = sp.getString("Cookie", ""); + } catch (Exception e) { + edit.putString("Cookie", ""); + wl.Cookie = ""; + bj = true; + } + if (bj) { + edit.commit(); + } + + wl.Cookie = main.sp.getString("Cookie", ""); + if (wl.Cookie.isEmpty()) { + new visitor(); + } + SharedPreferences theme = getSharedPreferences("theme", MODE_PRIVATE); + @SuppressLint("CommitPrefEdits") SharedPreferences.Editor edit = theme.edit(); + int i = theme.getInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + if (i == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) { + edit.putInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + edit.apply(); + } + AppCompatDelegate.setDefaultNightMode(i); + String jsonList = this.getSharedPreferences("list", Context.MODE_PRIVATE) + .getString("listData", null); // 获取保存的 JSON 字符串 + if (jsonList != null) { + Type type = new TypeToken>() { + }.getType(); + bfqkz.list = new com.google.gson.Gson().fromJson(jsonList, type); + // 将 JSON 字符串转换回列表数据 + } + bfqkz.xm = wj.getMP3FromFile(); + if (bfqkz.xm != null) { + new Thread() { + @Override + public void run() { + super.run(); + MP3 hq = com.muqingbfq.api.url.hq(bfqkz.xm); + if (hq == null) { + return; + } + try { + bfqkz.mt.DataSource(hq); + } catch (Exception e) { + com.muqingbfq.mq.gj.sc(e); + } + } + }.start(); + } + registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) { + } + @Override + public void onActivityStarted(@NonNull Activity activity) { + if (count == 0) { //后台切换到前台 + if (FloatingLyricsService.lei != null) { + stopService(new Intent(main.this, FloatingLyricsService.class)); + } + } + count++; + } + @Override + public void onActivityResumed(@NonNull Activity activity) { + } + @Override + public void onActivityPaused(@NonNull Activity activity) { + } + @Override + public void onActivityStopped(@NonNull Activity activity) { + count--; + if (count == 0) { //后台切换到前台 + new Thread(){ + @Override + public void run() { + try { + sleep(1000); + } catch (InterruptedException e) { + gj.sc(e); + } + if (count != 0) { + return; + } + if (!FloatingLyricsService.get()) { + return; + } + if (Settings.canDrawOverlays(main.this)) { + startService(new Intent(main.this, FloatingLyricsService.class)); + } + super.run(); + } + }.start(); + } + } + + @Override + public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { + } + @Override + public void onActivityDestroyed(@NonNull Activity activity) { + } + }); + } +} diff --git a/app/src/main/java/com/muqingbfq/mq/AppCompatActivity.java b/app/src/main/java/com/muqingbfq/mq/AppCompatActivity.java new file mode 100644 index 0000000..3d770eb --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/AppCompatActivity.java @@ -0,0 +1,25 @@ +package com.muqingbfq.mq; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.annotation.Nullable; +import androidx.viewbinding.ViewBinding; + +import com.jaeger.library.StatusBarUtil; + +public abstract class AppCompatActivity extends androidx.appcompat.app.AppCompatActivity { + + protected abstract ViewBindingType getViewBindingObject(LayoutInflater layoutInflater); + + protected ViewBindingType getViewBinding() { + binding = getViewBindingObject(getLayoutInflater()); + return binding; + } + + public ViewBindingType binding; + public void setContentView() { + super.setContentView(getViewBinding().getRoot()); + } +} diff --git a/app/src/main/java/com/muqingbfq/mq/BluetoothMusicController.java b/app/src/main/java/com/muqingbfq/mq/BluetoothMusicController.java new file mode 100644 index 0000000..fa8704c --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/BluetoothMusicController.java @@ -0,0 +1,46 @@ +package com.muqingbfq.mq; + +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.content.ComponentName; +import android.content.Context; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.support.v4.media.session.MediaSessionCompat; + +import com.muqingbfq.MyButtonClickReceiver; + +public class BluetoothMusicController { + private Context context; + private MediaSessionCompat mediaSession; + + public BluetoothMusicController(Context context) { + this.context = context; + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); + intentFilter.addAction("android.intent.action.HEADSET_PLUG"); + context.registerReceiver(new MyButtonClickReceiver(), intentFilter); + registerHeadsetReceiver(); + } + + public void stop() { + unregisterHeadsetReceiver(); + } + + // 注册媒体按钮事件接收器 + @SuppressLint("ObsoleteSdkInt") + public void registerHeadsetReceiver() { + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + ComponentName name = new ComponentName(context.getPackageName(), MyButtonClickReceiver.class.getName()); + audioManager.registerMediaButtonEventReceiver(name); + } + + // 注销媒体按钮事件接收器 + public void unregisterHeadsetReceiver() { + if (mediaSession != null) { + mediaSession.setActive(false); + mediaSession.release(); + mediaSession = null; + } + } +} diff --git a/app/src/main/java/com/muqingbfq/mq/BlurGradientTransformation.java b/app/src/main/java/com/muqingbfq/mq/BlurGradientTransformation.java new file mode 100644 index 0000000..9fbb530 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/BlurGradientTransformation.java @@ -0,0 +1,100 @@ +package com.muqingbfq.mq; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; + +import java.security.MessageDigest; + +public class BlurGradientTransformation extends BitmapTransformation { + private static final String TAG = "BlurGradientTrans"; + private RenderScript mRenderScript; + private ScriptIntrinsicBlur mScriptIntrinsicBlur; + private float mRadius = 25f; + private int[] mGradientColors = new int[]{0x66000000, 0xcc000000}; + private float[] mGradientPositions = new float[]{0f, 1f}; + + public BlurGradientTransformation(Context context) { + super(); + mRenderScript = RenderScript.create(context); + mScriptIntrinsicBlur = ScriptIntrinsicBlur.create(mRenderScript, Element.U8_4(mRenderScript)); + } + + public BlurGradientTransformation(Context context, float radius) { + this(context); + mRadius = radius; + } + + @Override + protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) { + Bitmap source = pool.get(toTransform.getWidth(), toTransform.getHeight(), toTransform.getConfig()); + if (source == null) { + source = Bitmap.createBitmap(toTransform.getWidth(), toTransform.getHeight(), toTransform.getConfig()); + } + Canvas canvas = new Canvas(source); + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setShader(new BitmapShader(toTransform, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); + canvas.drawRoundRect(new RectF(0, 0, toTransform.getWidth(), toTransform.getHeight()), 30f, 30f, paint); + Allocation input = Allocation.createFromBitmap(mRenderScript, source, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + Allocation output = Allocation.createTyped(mRenderScript, input.getType()); + mScriptIntrinsicBlur.setInput(input); + mScriptIntrinsicBlur.setRadius(mRadius); + mScriptIntrinsicBlur.forEach(output); + output.copyTo(source); + + // 绘制渐变色 + Canvas canvas1 = new Canvas(source); + Paint paint1 = new Paint(); + paint1.setAntiAlias(true); + paint1.setShader(new android.graphics.LinearGradient(0, 0, 0, source.getHeight(), mGradientColors, mGradientPositions, Shader.TileMode.CLAMP)); + paint1.setXfermode(null); + canvas1.drawRect(new Rect(0, 0, source.getWidth(), source.getHeight()), paint1); + + return source; + } + + @Override + public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { + messageDigest.update("BlurGradientTransformation".getBytes()); + } + + @Override + public int hashCode() { + return BlurGradientTransformation.class.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BlurGradientTransformation; + } + + public BlurGradientTransformation setRadius(float radius) { + mRadius = radius; + return this; + } + + public BlurGradientTransformation setGradientColors(int[] colors) { + mGradientColors = colors; + return this; + } + + public BlurGradientTransformation setGradientPositions(float[] positions) { + mGradientPositions = positions; + return this; + } +} diff --git a/app/src/main/java/com/muqingbfq/mq/EditViewDialog.java b/app/src/main/java/com/muqingbfq/mq/EditViewDialog.java new file mode 100644 index 0000000..71c0435 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/EditViewDialog.java @@ -0,0 +1,167 @@ +package com.muqingbfq.mq; +import android.annotation.SuppressLint; +import android.content.Context; +import android.text.InputType; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.muqingbfq.R; +import com.muqingbfq.main; + +public class EditViewDialog { + + public EditText editText; + public Button buttona, buttonb; + public MaterialAlertDialogBuilder AlertDialogBuilder; + private AlertDialog alertDialog; + + public EditViewDialog(@NonNull Context context, String str) { + AlertDialogBuilder = new MaterialAlertDialogBuilder(context); + setTitle(str); + @SuppressLint("InflateParams") View inflate = LayoutInflater.from(context).inflate(R.layout.view_edit, null); + editText = inflate.findViewById(R.id.editview); + buttona = inflate.findViewById(R.id.button1); + buttonb = inflate.findViewById(R.id.button2); + AlertDialogBuilder.setView(inflate); + buttona.setOnClickListener(view -> dismiss()); + buttonb.setOnClickListener(view -> dismiss()); + } + public EditViewDialog setTitle(String str) { + AlertDialogBuilder.setTitle(str); + return this; + } + + public EditViewDialog setMessage(String str) { + AlertDialogBuilder.setMessage(str); + return this; + } + + public AlertDialog show() { + alertDialog = AlertDialogBuilder.show(); + alertDialog.setCancelable(true); + alertDialog.setCanceledOnTouchOutside(false); + return alertDialog; + } + + public String getEditText() { + return editText.getText().toString(); + } + + public EditViewDialog setEditinputType(String str) { + int inputType; + switch (str) { + case "textCapCharacters": + inputType = InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS; + break; + case "textCapWords": + inputType = InputType.TYPE_TEXT_FLAG_CAP_WORDS; + break; + case "textCapSentences": + inputType = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; + break; + case "textAutoCorrect": + inputType = InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; + break; + case "textAutoComplete": + inputType = InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; + break; + case "textMultiLine": + inputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE; + break; + case "textImeMultiLine": + inputType = InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE; + break; + case "textNoSuggestions": + inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; + break; + case "textUri": + inputType = InputType.TYPE_TEXT_VARIATION_URI; + break; + case "textEmailAddress": + inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; + break; + case "textEmailSubject": + inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT; + break; + case "textShortMessage": + inputType = InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE; + break; + case "textLongMessage": + inputType = InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE; + break; + case "textPersonName": + inputType = InputType.TYPE_TEXT_VARIATION_PERSON_NAME; + break; + case "textPostalAddress": + inputType = InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS; + break; + case "textPassword": + inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD; + break; + case "textVisiblePassword": + inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + break; + case "textWebEditText": + inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; + break; + case "textFilter": + inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER; + break; + case "textPhonetic": + inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC; + break; + case "number": + inputType = InputType.TYPE_CLASS_NUMBER; + break; + case "numberSigned": + inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED; + break; + case "numberDecimal": + inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL; + break; + case "phone": + inputType = InputType.TYPE_CLASS_PHONE; + break; + case "datetime": + inputType = InputType.TYPE_CLASS_DATETIME; + break; + case "date": + inputType = InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_DATE; + break; + case "time": + inputType = InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_TIME; + break; + case "none": + case "text": + default: + inputType = InputType.TYPE_CLASS_TEXT; + break; + } + editText.setInputType(inputType); + return this; + } + + public void dismiss() { + main.handler.post(() -> alertDialog.dismiss()); + } + + public void setNegative(View.OnClickListener a) { + buttona.setOnClickListener(a); + } + + public EditViewDialog setPositive(View.OnClickListener a) { + buttonb.setOnClickListener(a); + return this; + } + + public interface OnClickListener extends View.OnClickListener { + @Override + void onClick(View view); + } +} diff --git a/app/src/main/java/com/muqingbfq/mq/FloatingLyricsService.java b/app/src/main/java/com/muqingbfq/mq/FloatingLyricsService.java new file mode 100644 index 0000000..7c4c52e --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/FloatingLyricsService.java @@ -0,0 +1,257 @@ +package com.muqingbfq.mq; + +import android.annotation.SuppressLint; +import android.app.Service; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; + +import androidx.annotation.Nullable; + +import com.dirror.lyricviewx.LyricEntry; +import com.dirror.lyricviewx.LyricViewX; +import com.google.android.material.card.MaterialCardView; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.muqingbfq.R; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.FloatLrcviewBinding; +import com.muqingbfq.main; + +import java.io.File; +import java.lang.reflect.Type; + +public class FloatingLyricsService extends Service implements View.OnClickListener, View.OnTouchListener { + private WindowManager windowManager; + FloatLrcviewBinding binding; + + public Runnable updateSeekBar = new Runnable() { + @Override + public void run() { + int index = 0; + for (int i = 0; i < LyricViewX.lyricEntryList.size(); i++) { + LyricEntry lineLrc = LyricViewX.lyricEntryList.get(i); + if (lineLrc.time <= bfqkz.mt.getCurrentPosition()) { + index = i; + } else { + break; + } + } + if (index < LyricViewX.lyricEntryList.size()) { + LyricEntry currentLrc = LyricViewX.lyricEntryList.get(index); + binding.lrcView.setText(currentLrc.text); + if (currentLrc.secondText != null) { + binding.lrcViewMessage.setText(currentLrc.secondText); + }else{ + binding.lrcViewMessage.setText(""); + } + } + handler.postDelayed(this, 1000); // 每秒更新一次进度 + } + }; + @SuppressLint("StaticFieldLeak") + public static FloatingLyricsService lei; + + public static boolean get() { + File file = new File(wj.filesdri + "FloatingLyricsService.json"); + if (file.exists() && file.isFile()) { + String dqwb = wj.dqwb(file.toString()); + Gson gson = new Gson(); + Type type = new TypeToken() { + }.getType(); + SETUP setup = gson.fromJson(dqwb, type); + return setup.i != 0; + } else { + return false; + } + } + + Handler handler = new Handler(); + WindowManager.LayoutParams params; + + public static class SETUP { + //0是关闭 1是打开 2是锁定 + public int i = 1, size = 20; + public float Alpha = 0.9f; + public String Color = "#0088FF"; + public int Y = 0; + } + + public SETUP setup = new SETUP(); + + public int lock() { + if (setup != null && setup.i == 2) { + return WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + } + return WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + } + + @Override + public void onCreate() { + super.onCreate(); + lei = this; + File file = new File(wj.filesdri + "FloatingLyricsService.json"); + try { + if (file.exists() && file.isFile()) { + String dqwb = wj.dqwb(file.toString()); + Gson gson = new Gson(); + Type type = new TypeToken() { + }.getType(); + setup = gson.fromJson(dqwb, type); + } + // 创建悬浮窗歌词的 View +// FloatLrcviewBinding + binding = FloatLrcviewBinding.inflate(LayoutInflater.from(this)); +// binding.getRoot().setOnTouchListener(this); +// int i = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;FLAG_NOT_TOUCH_MODAL + params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT, + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : + WindowManager.LayoutParams.TYPE_PHONE, + lock(), + PixelFormat.TRANSLUCENT + ); + + params.y = setup.Y; + + binding.getRoot().setAlpha(setup.Alpha); + float v = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, + setup.size, + getResources().getDisplayMetrics()); + binding.lrcView.setTextSize(v); + binding.lrcView.setTextColor(Color.parseColor(setup.Color)); + binding.lrcViewMessage.setTextSize(v-1.0f); + binding.lrcViewMessage.setTextColor(Color.parseColor(setup.Color)); + binding.lock.setOnClickListener(this); +// params.gravity = Gravity.CENTER; + + + // 获取 WindowManager 并将悬浮窗歌词添加到窗口中 + windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + windowManager.addView(binding.getRoot(), params); + if (setup.i == 2) { + setyc(); + + } else { + show(); + } + /* + if (setup.i == 0) + setup.i = 1; + baocun();*/ + handler.post(updateSeekBar); // 在播放开始时启动更新进度 + } catch (Exception e) { + wj.sc(file.toString()); + gj.sc(getClass() + ":" + e); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + // 在 Service 销毁时移除悬浮窗歌词 + if (windowManager != null && binding != null) { + windowManager.removeView(binding.getRoot()); + handler.removeCallbacks(updateSeekBar); // 在播放开始时启动更新进度 + } + lei = null; + } + + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + public void baocun() { + wj.xrwb(new File(wj.filesdri + "FloatingLyricsService.json").toString(), + new Gson().toJson(setup)); + } + + public static void baocun(SETUP setup) { + if (setup == null) { + return; + } + + wj.xrwb(new File(wj.filesdri + "FloatingLyricsService.json").toString(), + new Gson().toJson(setup)); + } + + private int initialY; + private float initialTouchY; + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + // 记录触摸事件的初始位置和坐标 + initialY = params.y; + initialTouchY = motionEvent.getRawY(); + return true; + case MotionEvent.ACTION_MOVE: + // 计算触摸事件的偏移量,将悬浮窗口的位置设置为初始位置加上偏移量 + int offsetY = (int) (motionEvent.getRawY() - initialTouchY); + setup.Y = initialY + offsetY; + params.y = setup.Y; + windowManager.updateViewLayout(binding.getRoot(), params); + return true; + case MotionEvent.ACTION_UP: + baocun(); + break; + + } + return false; + } + + @Override + public void onClick(View view) { + int id = view.getId(); + if (id == R.id.kg) { + ImageView kg = (ImageView) view; + if (bfqkz.mt == null) { + return; + } + if (bfqkz.mt.isPlaying()) { + bfqkz.mt.pause(); + kg.setImageResource(R.drawable.zt); + } else { + bfqkz.mt.start(); + kg.setImageResource(R.drawable.bf); + } + } else if (id == R.id.lock) { + setyc(); + } + } + + public void setyc() { + setup.i = 2; + params.flags = lock(); + binding.lock.setVisibility(View.GONE); + windowManager.updateViewLayout(binding.getRoot(), params); + baocun(); + } + + @SuppressLint("CheckResult") + public void show() { + setup.i = 1; + params.flags = lock(); + binding.lock.setVisibility(View.VISIBLE); + windowManager.updateViewLayout(binding.getRoot(), params); + baocun(); + } +} diff --git a/app/src/main/java/com/muqingbfq/mq/Fragment.java b/app/src/main/java/com/muqingbfq/mq/Fragment.java new file mode 100644 index 0000000..709098a --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/Fragment.java @@ -0,0 +1,27 @@ +package com.muqingbfq.mq; + +import android.graphics.Rect; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.viewbinding.ViewBinding; + +public abstract class Fragment extends androidx.fragment.app.Fragment { + + protected abstract Binding inflateViewBinding(LayoutInflater inflater,ViewGroup container); + + public Binding binding; + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding=inflateViewBinding(inflater,container); + setUI(inflater, container, savedInstanceState); + return binding.getRoot(); + } + + public abstract void setUI(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState); +} diff --git a/app/src/main/java/com/muqingbfq/mq/FragmentActivity.java b/app/src/main/java/com/muqingbfq/mq/FragmentActivity.java new file mode 100644 index 0000000..969824d --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/FragmentActivity.java @@ -0,0 +1,53 @@ +package com.muqingbfq.mq; + +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.viewbinding.ViewBinding; + +import com.jaeger.library.StatusBarUtil; +import com.muqingbfq.R; + +public abstract class FragmentActivity + extends androidx.appcompat.app.AppCompatActivity { + + protected abstract ViewBindingType getViewBindingObject(LayoutInflater layoutInflater); + + protected ViewBindingType getViewBinding() { + binding = getViewBindingObject(getLayoutInflater()); + return binding; + } + + public ViewBindingType binding; + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + } + return super.onOptionsItemSelected(item); + } + public void setToolbar() { + View viewById = findViewById(R.id.toolbar); + if (viewById != null) { + setSupportActionBar((Toolbar) viewById); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + } + @Override + public void setContentView(int view) { + super.setContentView(view); + } + public void setContentView() { + binding = getViewBindingObject(getLayoutInflater()); + setContentView(binding.getRoot()); + setToolbar(); + } + + @Override + public void setContentView(View view) { + super.setContentView(view); + } +} diff --git a/app/src/main/java/com/muqingbfq/mq/NotificationManagerCompat.java b/app/src/main/java/com/muqingbfq/mq/NotificationManagerCompat.java new file mode 100644 index 0000000..fcca301 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/NotificationManagerCompat.java @@ -0,0 +1,176 @@ +package com.muqingbfq.mq; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.os.Build; +import android.support.v4.media.MediaMetadataCompat; + +import androidx.core.app.ActivityCompat; +import androidx.core.app.NotificationCompat; + +import com.muqingbfq.MyButtonClickReceiver; +import com.muqingbfq.R; +import com.muqingbfq.bfqkz; +import com.muqingbfq.yc; + +public class NotificationManagerCompat { + bfqkz context; + public NotificationCompat.Builder notificationBuilder; + public androidx.core.app.NotificationManagerCompat notificationManager; + private String name, zz; + + public NotificationManagerCompat(bfqkz context) { + this.context = context; + name = context.getString(R.string.app_name); + zz = context.getString(R.string.zz); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, + NotificationManager.IMPORTANCE_LOW); + channel.setDescription(zz); + NotificationManager systemService = context.getSystemService(NotificationManager.class); + systemService.createNotificationChannel(channel); + if (!systemService.areNotificationsEnabled()) { + return; + } + } + // 适配12.0及以上 + + // 设置启动的程序,如果存在则找出,否则新的启动 + Intent my = new Intent(context, MyButtonClickReceiver.class); + pendingIntent_kg = getBroadcast(context, my. + setAction("kg")); + pendingIntent_syq = getBroadcast(context, my. + setAction("syq")); + pendingIntent_xyq = getBroadcast(context, my. + setAction("xyq")); + pendingIntent_lrc = getBroadcast(context, my. + setAction("lrc")); + pendingIntent_like = getBroadcast(context, my. + setAction("like")); + style = new androidx.media.app.NotificationCompat.MediaStyle() + .setShowActionsInCompactView(1, 2, 3) + .setMediaSession(context.mSession.getSessionToken()); + notificationManager = androidx.core.app.NotificationManagerCompat.from(context); + notificationBuilder = getNotificationBuilder(context) + .setSmallIcon(R.drawable.foreground) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true).setColorized(true).setShowWhen(false) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setContentIntent(context.pendingIntent) + .setStyle(style); + tzl(); + } catch (Exception e) { + yc.start(context, e); + } + } + + androidx.media.app.NotificationCompat.MediaStyle style; + + @SuppressLint("RestrictedApi") + public void tzl_button() { + if (notificationBuilder == null) { + return; + } + notificationBuilder.mActions.clear(); + notificationBuilder + .addAction(R.drawable.like, "like", pendingIntent_like) // #0 + .addAction(R.drawable.syq, "syq", pendingIntent_syq) // #0 + .addAction(bfqkz.mt.isPlaying() ? R.drawable.bf : R.drawable.zt + , "kg", pendingIntent_kg) // #1 + .addAction(R.drawable.xyq, "xyq", pendingIntent_xyq) + .addAction(R.drawable.lock, "lrc", pendingIntent_lrc) + .setOngoing(bfqkz.mt.isPlaying()); + notificationManager_notify(); + } + + public void setbitmap(Bitmap bitmap) { + notificationBuilder + .setLargeIcon(bitmap); + + context.builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, name) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, zz) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, zz) + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, 100); + + context.mSession.setMetadata(context.builder.build()); + notificationManager_notify(); + } + + @SuppressLint("RestrictedApi") + public void tzl() { + if (notificationBuilder == null) { + return; + } + if (bfqkz.xm != null) { + name = bfqkz.xm.name; + zz = bfqkz.xm.zz; + } + notificationBuilder.mActions.clear(); + notificationBuilder + .addAction(R.drawable.like, "like", pendingIntent_like) // #0 + .addAction(R.drawable.syq, "syq", pendingIntent_syq) // #0 + .addAction(bfqkz.mt.isPlaying() ? R.drawable.bf : R.drawable.zt + , "kg", pendingIntent_kg) // #1 + .addAction(R.drawable.xyq, "xyq", pendingIntent_xyq) + .addAction(R.drawable.lock, "lrc", pendingIntent_lrc) + .setContentTitle(name) + .setContentText(zz) + .setOngoing(bfqkz.mt.isPlaying()); + + + context.mSession.setMetadata(context.builder.build()); + notificationManager_notify(); + } + + private PendingIntent pendingIntent_kg, + pendingIntent_syq, + pendingIntent_xyq, + pendingIntent_lrc, + pendingIntent_like; + + private final String CHANNEL_ID = "MediaSessionCompat"; + + public void notificationManager_notify() { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + return; + } + notificationManager.notify(0, notificationBuilder.build()); + } + + private NotificationCompat.Builder getNotificationBuilder(Context context) { + // 适用于Android 8.0及以上版本 +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + return new NotificationCompat.Builder(context, CHANNEL_ID); + } + + private PendingIntent getBroadcast(Context context, Intent intent) { + int flag; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + flag = PendingIntent.FLAG_IMMUTABLE; + } else { + flag = PendingIntent.FLAG_UPDATE_CURRENT; + } + return PendingIntent.getBroadcast(context, 0, intent, flag); + } + + + public static PendingIntent getActivity(Context context, Intent intent) { + int flag; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + flag = PendingIntent.FLAG_IMMUTABLE; + } else { + flag = PendingIntent.FLAG_UPDATE_CURRENT; + } + return PendingIntent.getActivity(context, 0, intent, flag); + } + +} diff --git a/app/src/main/java/com/muqingbfq/mq/RecyclerAdapter.java b/app/src/main/java/com/muqingbfq/mq/RecyclerAdapter.java new file mode 100644 index 0000000..8f6387e --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/RecyclerAdapter.java @@ -0,0 +1,13 @@ +package com.muqingbfq.mq; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +public class RecyclerAdapter { + +} diff --git a/app/src/main/java/com/muqingbfq/mq/VH.java b/app/src/main/java/com/muqingbfq/mq/VH.java new file mode 100644 index 0000000..ec57bc8 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/VH.java @@ -0,0 +1,16 @@ +package com.muqingbfq.mq; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +public class VH extends RecyclerView.ViewHolder { + + public bind binding; + public VH(bind itemView) { + super(itemView.getRoot()); + binding = itemView; + } +} diff --git a/app/src/main/java/com/muqingbfq/mq/floating.java b/app/src/main/java/com/muqingbfq/mq/floating.java new file mode 100644 index 0000000..44118ca --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/floating.java @@ -0,0 +1,172 @@ +package com.muqingbfq.mq; + +import android.annotation.SuppressLint; +import android.app.Service; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.Build; +import android.os.IBinder; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.muqingbfq.R; +import com.muqingbfq.main; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +public class floating extends Service { + private static RecyclerView.Adapter lbspq; + + class spq extends RecyclerView.Adapter { + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(new TextView(parent.getContext())); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + String s = list.get(position); + holder.textView.setText(s); + } + + @Override + public int getItemCount() { + return list.size(); + } + } + + public static void start(Context context) { + if (Settings.canDrawOverlays(context)) { + context.startService(new Intent(context, floating.class)); + } + } + + public static void end(Context context) { + Intent serviceIntent = new Intent(context, floating.class); + context.stopService(serviceIntent); + } + + public static List list; + private WindowManager windowManager; + private View view; + private View image, layout; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @SuppressLint("NotifyDataSetChanged") + public static void addtext(String str) { + if (lbspq == null || list == null) { + return; + } + SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.CHINA); // 创建一个 SimpleDateFormat 对象,指定时间格式 + String formattedDate = sdf.format(new Date()); // 格式化当前时间 + list.add(0, formattedDate + ": " + str); + main.handler.post(lbspq::notifyDataSetChanged); + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public void onCreate() { + super.onCreate(); + list = new ArrayList<>(); + lbspq = new spq(); + view = LayoutInflater.from(this).inflate(R.layout.floating_sc, null); + layout = view.findViewById(R.id.view1); + ViewGroup.LayoutParams layoutParams = layout.getLayoutParams(); + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + layoutParams.height = displayMetrics.heightPixels - displayMetrics.heightPixels / 2 / 2; + layoutParams.width = displayMetrics.widthPixels - displayMetrics.widthPixels / 2 / 2; + layout.setLayoutParams(layoutParams); + layout.setVisibility(View.GONE); + image = view.findViewById(R.id.image); + image.setOnClickListener(vw -> { + layout.setVisibility(View.VISIBLE); + vw.setVisibility(View.GONE); + }); + view.findViewById(R.id.text4).setOnClickListener(view -> { + layout.setVisibility(View.GONE); + image.setVisibility(View.VISIBLE); + }); + RecyclerView recyclerView = view.findViewById(R.id.list); + recyclerView.setAdapter(lbspq); + addtext("Android stdio 2022.3.1版-调试器"); + + //清空按钮 + view.findViewById(R.id.text1).setOnClickListener(view -> { + list.clear(); + lbspq.notifyDataSetChanged(); + }); + //复制按钮 + view.findViewById(R.id.text2).setOnClickListener(view -> { + // 获取剪贴板管理器 + ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText("label", list.get(0))); + gj.ts(this,"成功复制了第一个数据"); + }); + //关闭按钮 + view.findViewById(R.id.text3).setOnClickListener(view -> stopSelf()); + int i = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : + WindowManager.LayoutParams.TYPE_PHONE, + i, + PixelFormat.TRANSLUCENT + ); + params.x = -displayMetrics.widthPixels; + + windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + windowManager.addView(view, params); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (view != null && windowManager != null) { + lbspq = null; + list = null; + windowManager.removeView(view); + } + } + + private static class ViewHolder extends RecyclerView.ViewHolder { + public TextView textView; + + public ViewHolder(View itemview) { + super(itemview); + textView = (TextView) itemview; + textView.setTextColor(Color.WHITE); + textView.setOnLongClickListener(view -> { + // 获取剪贴板管理器 + ClipboardManager clipboard = (ClipboardManager) view.getContext(). + getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText("label", list.get(0))); + gj.ts(view.getContext(), "复制成功"); + return false; + }); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/mq/gj.java b/app/src/main/java/com/muqingbfq/mq/gj.java new file mode 100644 index 0000000..fe75fe7 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/gj.java @@ -0,0 +1,200 @@ +package com.muqingbfq.mq; + +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.Service; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.net.Uri; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.muqingbfq.main; +import com.muqingbfq.yc; + +import org.json.JSONObject; + +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Collections; +import java.util.List; + +public class gj { + public static void ts(Context a, Object b) { + Toast.makeText(a, b.toString(), Toast.LENGTH_SHORT).show(); + } + + public static void xcts(Context context, Object b) { + main.handler.post(() -> Toast.makeText(context, b.toString(), Toast.LENGTH_SHORT).show()); + + } + + public static boolean isAppInForeground(Context context) { + ActivityManager activityManager = (ActivityManager) context.getSystemService(Service.ACTIVITY_SERVICE); + List runningAppProcessInfoList = activityManager.getRunningAppProcesses(); + + if (runningAppProcessInfoList == null) { + Log.d("runningAppProcess:", "runningAppProcessInfoList is null!"); + return false; + } + for (ActivityManager.RunningAppProcessInfo processInfo : runningAppProcessInfoList) { + if (processInfo.processName.equals(context.getPackageName()) && (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND)) { + return true; + } + } + return false; + } + + public static boolean isTablet(Context context) { + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + return dm.widthPixels > dm.heightPixels; + } + + public static int getThemeColor(Context context, int id) { + TypedValue typedValue = new TypedValue(); + if (context.getTheme().resolveAttribute(id, typedValue, true)) { + return typedValue.data; + } else { + return -1; + } + } + public static void sc(Object a) { + if (a == null) { + a = "null"; + } + Log.d("打印", a.toString()); + floating.addtext(a.toString()); + } + + public static void llq(Context context, String str) { + context.startActivity(new Intent(context, llq.class).putExtra("url", str)); + } + + public static void fx(Context context, String str) { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType("text/plain"); + shareIntent.putExtra(Intent.EXTRA_TEXT, str); + context.startActivity(shareIntent); + } + + /** + * 复制文字到剪切板 + */ + public static void fz(Context context, String text) { + ClipboardManager systemService = + (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + // 创建能够存入剪贴板的ClipData对象 + //‘Label’这是任意文字标签 + ClipData mClipData = ClipData.newPlainText("Label", text); + //将ClipData数据复制到剪贴板: + systemService.setPrimaryClip(mClipData); + gj.ts(context, "复制成功"); + } + + public static boolean isWiFiConnected() { + try { + for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) { + if (networkInterface.isUp() && !networkInterface.isLoopback()) { + if (networkInterface.getDisplayName().contains("wlan")) { + return true; // Wi-Fi网络 + } else if (networkInterface.getDisplayName().contains("rmnet")) { + return false; // 流量网络 + } + } + } + } catch (SocketException e) { + gj.sc(e); + } + return false; // 默认为流量网络 + } + + public static class jianchagengxin extends Thread { + Context context; + + public jianchagengxin(Context context) { + this.context = context; + if (!wj.cz(wj.filesdri + "gx.mq")) { + start(); + } + } + + @Override + public void run() { + super.run(); + jianchagengxin(context); + } + } + + public static int jianchagengxin(Context context) { + try { + String versionName = context.getPackageManager() + .getPackageInfo(context.getPackageName(), 0).versionName; + String hq = wl.get(main.http + "/muqingbfq?bb=" + versionName); + final JSONObject jsonObject = new JSONObject(hq); + boolean code = jsonObject.getInt("code") == 1; + String msg = jsonObject.getString("msg"); + if (code) { + String url = jsonObject.getString("url"); + String bb = jsonObject.getString("bb"); + main.handler.post(() -> new MaterialAlertDialogBuilder(context) + .setTitle("更新" + bb) + .setMessage(msg) + .setNegativeButton("取消", null) + .setPositiveButton("更新", (dialogInterface, i) -> context.startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse(url)))) + .show()); + } + //1表示需要更新 + return code ? 1 : 0; + } catch (Exception e) { + sc(e); + } + return 400; + } + + public static void tcjp(EditText editText) { + editText.requestFocus();//获取焦点 + InputMethodManager imm = (InputMethodManager) + editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); +// gj.sc(imm.isActive()); + //没有显示键盘,弹出 + imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); + } + + public static void ycjp(EditText editText) { + InputMethodManager imm = (InputMethodManager) + editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm.isActive()) //有显示键盘,隐藏 + imm.hideSoftInputFromWindow(editText.getWindowToken(), + InputMethodManager.HIDE_NOT_ALWAYS); + } + + public static int getztl(Context context) { + // 获得状态栏高度 + @SuppressLint({"InternalInsetResource", "DiscouragedApi"}) int resourceId = + context.getResources(). + getIdentifier("status_bar_height", "dimen", "android"); + return context.getResources().getDimensionPixelSize(resourceId); + } + + public static int getbackgroundColor(AppCompatActivity appCompatActivity) { + TypedArray array = appCompatActivity.getTheme().obtainStyledAttributes(new int[]{ + android.R.attr.colorBackground +// android.R.attr.textColorPrimary, + }); + int backgroundColor = array.getColor(0, 0xFF00FF); +// int textColor = array.getColor(1, 0xFF00FF); + array.recycle(); + return backgroundColor; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/mq/llq.java b/app/src/main/java/com/muqingbfq/mq/llq.java new file mode 100644 index 0000000..46edadc --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/llq.java @@ -0,0 +1,217 @@ +package com.muqingbfq.mq; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.muqingbfq.R; +import com.muqingbfq.databinding.ActivityLlqBinding; +import com.muqingbfq.databinding.ViewDownloadBinding; +import com.muqingbfq.main; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class llq extends AppCompatActivity { + WebView web; + + @Override + protected ActivityLlqBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityLlqBinding.inflate(layoutInflater); + } + + @SuppressLint("SetJavaScriptEnabled") + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(); + Intent intent = getIntent(); + setSupportActionBar(binding.toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + web = binding.webview; + web.getSettings().setJavaScriptEnabled(true); + web.getSettings().setDomStorageEnabled(true); +// 禁用缓存 + web.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); + web.setWebViewClient(new WebViewClient() { + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + setTitle(web.getTitle()); + // 在这里获取到了网页的标题 + } + }); + web.setDownloadListener((url1, userAgent, contentDisposition, mimetype, contentLength) -> { + String size = "0B"; + if (contentLength > 0) { + final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"}; + int digitGroups = (int) (Math.log10(contentLength) / Math.log10(1024)); + size = String.format(Locale.getDefault(), "%.1f %s", contentLength + / Math.pow(1024, digitGroups), units[digitGroups]); + } + final String filename = url1.substring(url1.lastIndexOf('/') + 1); + new MaterialAlertDialogBuilder(llq.this) + .setTitle(filename) + .setMessage("文件链接:" + url1 + + "\n文件大小:" + size) + .setNegativeButton("取消", null) + .setNegativeButton("下载", (dialogInterface, i) -> { + // 检查权限 + if (ContextCompat.checkSelfPermission(llq.this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + // 如果没有写入存储的权限,则请求权限 + ActivityCompat.requestPermissions(llq.this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + 1); + } else { + ViewDownloadBinding inflate = ViewDownloadBinding.inflate(getLayoutInflater()); + AlertDialog show = new MaterialAlertDialogBuilder(llq.this) + .setTitle(String.format(Locale.getDefault() + , "文件名称:%s", filename)) + .setMessage(String.format(Locale.getDefault() + , "下载路径:%s", Environment. + getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS).getAbsolutePath())) + .setView(inflate.getRoot()) + .show(); + OkHttpClient okHttpClient = new OkHttpClient(); + Request build = new Request.Builder() + .url(url1) + .build(); + okHttpClient.newCall(build).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + InputStream inputStream = response.body().byteStream(); + long l = response.body().contentLength(); + File file = new File(Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), + filename); + FileOutputStream fileOutputStream = null; + try { + int read; + fileOutputStream = new FileOutputStream(file); + byte[] bytes = new byte[2048]; + long downloadedSize = 0; + while ((read = inputStream.read(bytes)) != -1) { + fileOutputStream.write(bytes, 0, read); + downloadedSize += read; + int progress = (int) ((100 * downloadedSize) / l); + main.handler.post(() -> inflate.textview.setText( + String.format(Locale.getDefault(), + "%d%%", progress))); + } + fileOutputStream.close(); + } catch (Exception e) { + gj.sc(e); + } finally { + if (fileOutputStream != null) { + fileOutputStream.close(); + } + } + main.handler.post(() -> { + gj.ts(llq.this, "下载完成"); + show.dismiss(); + }); + } + }); + } + }).show(); + }); + web.setWebChromeClient(new WebChromeClient() { + @Override + public void onProgressChanged(WebView view, int newProgress) { + super.onProgressChanged(view, newProgress); + if (newProgress == 100) { + binding.webViewProgressBar.setVisibility(View.GONE); + } else { + binding.webViewProgressBar.setProgress(newProgress); + if (!binding.webViewProgressBar.isShown()) { + binding.webViewProgressBar.setVisibility(View.VISIBLE); + } + } + } + }); + loadUrl(intent.getStringExtra("url")); + } + + private void loadUrl(String url) { + web.loadUrl(url); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == 1 && grantResults.length > 0 && + grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // 权限已授予,执行文件下载操作 + gj.ts(this, "权限已授予,请重新执行文件下载操作"); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.llq, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public void onBackPressed() { + if (web.canGoBack()) { + web.goBack(); + } else { + finish(); + } + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + finish(); + } else if (itemId == R.id.fx) { + gj.fx(this, web.getUrl()); +// 服务中心 + } else if (itemId == R.id.sx) { + web.reload(); + } else if (itemId == R.id.menu_web) { + startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse(web.getUrl()))); + } + return super.onOptionsItemSelected(item); + } + +} diff --git a/app/src/main/java/com/muqingbfq/mq/wj.java b/app/src/main/java/com/muqingbfq/mq/wj.java new file mode 100644 index 0000000..ff944be --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/wj.java @@ -0,0 +1,237 @@ +package com.muqingbfq.mq; + +import static androidx.core.content.ContextCompat.startActivity; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Environment; +import android.provider.Settings; +import android.util.Log; + +import androidx.core.app.ActivityCompat; + +import com.google.gson.Gson; +import com.muqingbfq.MP3; +import com.muqingbfq.home; +import com.muqingbfq.yc; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class wj { + public static String filesdri; + public static String mp3 = "mp3/"; + public static String lishi_json = "lishi.json"; + public static String gd = "gd/"; + public static String tx = "image/"; + public static String gd_json = "gd.json", gd_xz = "gd_xz.json", + gd_phb = "gd_phb.json", mp3_like = "mp3_like.json"; + + public wj(Context context) { + try { + wj.filesdri = context.getExternalFilesDir("").getAbsolutePath() + "/"; +// context.getFilesDir().toString() + "/"; + gd_json = filesdri + gd_json; + mp3 = filesdri + mp3; + gd = filesdri + gd; + gd_xz = filesdri + gd_xz; + gd_phb = filesdri + gd_phb; + mp3_like = gd + mp3_like; + tx = filesdri + tx; + } catch (Exception e) { + yc.start(context, e); + } + } + + /* + * 这里定义的是一个文件保存的方法,写入到文件中,所以是输出流 + * */ + public static boolean xrwb(String url, String text) { + if (text == null) { + text = ""; + } + File file = new File(url); +//如果文件不存在,创建文件 + try { + File parentFile = file.getParentFile(); + if (!parentFile.isDirectory()) { + parentFile.mkdirs(); + } + if (!file.exists()) + file.createNewFile(); +//创建FileOutputStream对象,写入内容 + FileOutputStream fos = new FileOutputStream(file); +//向文件中写入内容 + fos.write(text.getBytes()); + fos.close(); + } catch (Exception e) { + gj.sc(e); + } + return false; + } + + public static String dqwb(String url) { + try { + File file = new File(url); + if (!file.exists()) { + return null; + } + FileInputStream fis = new FileInputStream(file); + BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + StringBuilder str = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + str.append(line); + } + br.close(); + fis.close(); + return str.toString(); + } catch (Exception e) { + gj.sc(e); + } + return null; + } + + public static boolean cz(String url) { + return new File(url).exists(); + } + + + public static boolean sc(String url) { + File file = new File(url); + return file.delete(); + } + + public static void sc(File url) { + if (url.exists()) { + File[] files = url.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + // 递归调用,删除子文件夹及其内容 + sc(file); + } else { + file.delete(); // 删除文件 + } + } + } + url.delete(); // 删除当前文件夹 + } + } + + + public String convertToMd5(String url) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] messageDigest = md.digest(url.getBytes()); + StringBuilder hexString = new StringBuilder(); + for (byte value : messageDigest) { + String hex = Integer.toHexString(0xFF & value); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return null; + } + + public static void fz(String sourceFilePath, String targetFilePath) { + File sourceFile = new File(sourceFilePath); + File targetFile = new File(targetFilePath); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + try (InputStream in = Files.newInputStream(sourceFile.toPath()); + OutputStream out = Files.newOutputStream(targetFile.toPath())) { + byte[] buf = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(buf)) > 0) { + out.write(buf, 0, bytesRead); + } + // 文件复制完成 + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + // 保存MP3对象到文件 + public static void setMP3ToFile(MP3 mp3) { + if (mp3 == null) { + return; + } + Gson gson = new Gson(); + String json = gson.toJson(mp3); + xrwb(filesdri + "mp3.dat", json); + } + + // 从文件中加载MP3对象 + public static MP3 getMP3FromFile() { + Gson gson = new Gson(); + MP3 mp3 = null; + try { + File file = new File(filesdri + "mp3.dat"); + if (file.exists() && file.length() > 0) { + FileReader reader = new FileReader(file); + mp3 = gson.fromJson(reader, MP3.class); + reader.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return mp3; + } + + + private static final int REQUEST_EXTERNAL_STORAGE = 1; + private static String[] PERMISSIONS_STORAGE = { + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + + public static boolean isCD(Activity context) { + //检查权限(NEED_PERMISSION)是否被授权 PackageManager.PERMISSION_GRANTED表示同意授权 + if (Build.VERSION.SDK_INT >= 30) { + if (!Environment.isExternalStorageManager()) { + Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + context.startActivity(intent); + } else { + Log.i("swyLog", "Android 11以上,当前已有权限"); + return true; + } + } else { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != + PackageManager.PERMISSION_GRANTED) { + //申请权限 + ActivityCompat.requestPermissions(context + , PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE); + } else { + Log.i("swyLog", "Android 6.0以上,11以下,当前已有权限"); + return true; + } + } else { + Log.i("swyLog", "Android 6.0以下,已获取权限"); + return true; + } + } + return false; + } +} diff --git a/app/src/main/java/com/muqingbfq/mq/wl.java b/app/src/main/java/com/muqingbfq/mq/wl.java new file mode 100644 index 0000000..9406f9e --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/wl.java @@ -0,0 +1,107 @@ +package com.muqingbfq.mq; + + +import com.muqingbfq.XM; +import com.muqingbfq.main; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class wl { + public static String Cookie; + public static void setcookie(String cookie) { + wl.Cookie = cookie; + main.edit.putString("Cookie", cookie); + main.edit.commit(); + } + + public static String hq(String url) { + try { + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder() + .url(main.api + url) + .build(); + Response response = client.newCall(request).execute(); + if (response.body() != null) { + return response.body().string(); + } + } catch (Exception e) { + gj.sc("wl hq(Strnig) " + e); + } + return null; + } + + public static String post(String str, String[][] a) { + OkHttpClient client = new OkHttpClient().newBuilder() + .build(); +// MediaType mediaType = MediaType.parse("text/plain"); + MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM); + for (String[] b : a) { + builder.addFormDataPart(b[0], b[1]); + } + builder.addFormDataPart("cookie", Cookie); + + Request request = new Request.Builder() + .url(main.api + str) + .method("POST", builder.build()) + .addHeader("User-Agent", "Apifox/1.0.0 (https://apifox.com)") + .addHeader("Accept", "*/*") + .addHeader("Host", "139.196.224.229:3000") + .addHeader("Connection", "keep-alive") + .build(); + try { + Response response = client.newCall(request).execute(); + if (response.body() != null) { + return response.body().string(); + } + } catch (Exception e) { + gj.sc(e); + } + return null; + } + + public static JSONObject jsonpost(String str, String[][] a) { + try { + return new JSONObject(post(str, a)); + } catch (JSONException e) { + gj.sc(e); + return null; + } + } + + public static String get(String url) { + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder() + .url(url) + .build(); + try { + Response response = client.newCall(request).execute(); + if (response.body() != null) { + return response.body().string(); + } + } catch (Exception e) { + gj.sc("wl get(Strnig) " + e); + } + return null; + } + + public static class xz extends Thread { + String url; + XM x; + + public xz(String url, XM x) { + this.url = url; + this.x = x; + start(); + } + + } + +} diff --git a/app/src/main/java/com/muqingbfq/sz.java b/app/src/main/java/com/muqingbfq/sz.java new file mode 100644 index 0000000..eeaff0d --- /dev/null +++ b/app/src/main/java/com/muqingbfq/sz.java @@ -0,0 +1,261 @@ +package com.muqingbfq; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.fragment.app.Fragment; + +import com.colorpicker.ColorPickerView; +import com.colorpicker.builder.ColorPickerDialogBuilder; +import com.dirror.lyricviewx.LyricEntry; +import com.dirror.lyricviewx.LyricViewX; +import com.google.android.material.slider.Slider; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.muqingbfq.databinding.ActivitySzBinding; +import com.muqingbfq.databinding.ActivitySzSetlrcBinding; +import com.muqingbfq.mq.AppCompatActivity; +import com.muqingbfq.mq.FloatingLyricsService; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; + +import java.io.File; +import java.lang.reflect.Type; + +public class sz extends AppCompatActivity { + @Override + protected ActivitySzBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivitySzBinding.inflate(layoutInflater); + } + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + setContentView(); + setSupportActionBar(binding.toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setTitle(getString(R.string.sz)); + SharedPreferences theme = getSharedPreferences("theme", MODE_PRIVATE); + @SuppressLint("CommitPrefEdits") SharedPreferences.Editor edit = theme.edit(); + int i = theme.getInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + if (i == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) { + binding.switchA1.setChecked(true); + binding.switchA2.setEnabled(false); + } else { + binding.switchA1.setChecked(false); + binding.switchA2.setEnabled(true); + binding.switchA2.setChecked(i == AppCompatDelegate.MODE_NIGHT_YES); + } + binding.switchA1.setOnCheckedChangeListener((compoundButton, b) -> { + if (b) { +// 跟随系统设置切换颜色模式 + int ms = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; + AppCompatDelegate.setDefaultNightMode(ms); + edit.putInt("theme", ms); + edit.apply(); + } + binding.switchA2.setEnabled(!b); + }); + binding.switchA2.setOnCheckedChangeListener((compoundButton, b) -> { + if (compoundButton.isEnabled()) { + int ms; + if (b) { + ms = AppCompatDelegate.MODE_NIGHT_YES; + } else { + ms = AppCompatDelegate.MODE_NIGHT_NO; + } + AppCompatDelegate.setDefaultNightMode(ms); + edit.putInt("theme", ms); + edit.commit(); + } + }); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + } + return super.onOptionsItemSelected(item); + } + + public static class setlrc extends Fragment implements Slider.OnSliderTouchListener, + Slider.OnChangeListener { + ActivitySzSetlrcBinding binding; + boolean is = true; + ActivityResultLauncher LyricsService = + registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + if (Settings.canDrawOverlays(getContext())) { + getContext().startService(new Intent(getContext(), FloatingLyricsService.class)); + } else { + binding.switchA3.setChecked(false); + binding.slide1.setEnabled(true); + + } + }); + FloatingLyricsService.SETUP setup; + + class ThreadLrc extends Thread { + @Override + public void run() { + super.run(); + while (is) { + int index = 0; + for (int i = 0; i < LyricViewX.lyricEntryList.size(); i++) { + LyricEntry lineLrc = LyricViewX.lyricEntryList.get(i); + if (lineLrc.time <= bfqkz.mt.getCurrentPosition()) { + index = i; + } else { + break; + } + } + if (index < LyricViewX.lyricEntryList.size()) { + LyricEntry currentLrc = LyricViewX.lyricEntryList.get(index); + requireActivity().runOnUiThread(() -> { + if (currentLrc.secondText != null) { + binding.lrcViewMessage.setText(currentLrc.secondText); + } else { + binding.lrcViewMessage.setText(""); + } + binding.lrcView.setText(currentLrc.text); + }); + } + gj.sc("ThreadLrc"); + try { + sleep(1000); + } catch (InterruptedException e) { + break; + } + } + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = ActivitySzSetlrcBinding.inflate(inflater, container, false); + File file = new File(wj.filesdri + "FloatingLyricsService.json"); + if (file.exists() && file.isFile()) { + String dqwb = wj.dqwb(file.toString()); + Gson gson = new Gson(); + Type type = new TypeToken() { + }.getType(); + + binding.slide1.setEnabled(true); + setup = gson.fromJson(dqwb, type); + binding.slide1.setValue(setup.size); + + binding.lrcView.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, + setup.size, + getResources().getDisplayMetrics())); + binding.lrcView.setTextColor(Color.parseColor(setup.Color)); + + binding.lrcViewMessage.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, + setup.size, + getResources().getDisplayMetrics()) - 1.0f); + binding.lrcViewMessage.setTextColor(Color.parseColor(setup.Color)); + binding.lrclin.setOnClickListener(view -> ColorPickerDialogBuilder + .with(view.getContext()) + .setTitle("调色盘") + .initialColor(Color.parseColor(setup.Color)) + .wheelType(ColorPickerView.WHEEL_TYPE.FLOWER) + .density(6) + .setOnColorSelectedListener(selectedColor -> { + }) + .setPositiveButton("确定", (dialog, selectedColor, allColors) -> { + setup.Color = String.format("#%08X", selectedColor); + binding.lrcView.setTextColor(selectedColor); + binding.lrcViewMessage.setTextColor(selectedColor); + FloatingLyricsService.baocun(setup); + }) + .setNegativeButton("取消", null) + .build() + .show()); + binding.textSlide1.setText(String.valueOf(setup.size)); + if (setup.i != 0) { + binding.switchA3.setChecked(true); + } + new ThreadLrc().start(); + } + binding.switchA3.setOnCheckedChangeListener((compoundButton, b) -> { + if (b) { + if (setup != null) { + setup.i = 1; + } + if (!Settings.canDrawOverlays(getContext())) { + // 无权限,需要申请权限 + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + getContext().getPackageName())); + LyricsService.launch(intent); + } else { + getContext().startService(new Intent(getContext(), FloatingLyricsService.class)); + } + } else { + if (setup != null) { + setup.i = 0; + } + main.application.stopService(new Intent(main.application, + FloatingLyricsService.class)); + } + FloatingLyricsService.baocun(setup); + }); + binding.slide1.setLabelFormatter(value -> String.valueOf((int) value)); + binding.slide1.addOnChangeListener(this); + binding.slide1.addOnChangeListener(this); + binding.slide1.addOnSliderTouchListener(this); + + return binding.getRoot(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + is = false; + } + + @Override + public void onStartTrackingTouch(@NonNull Slider slider) { + + } + + @Override + public void onStopTrackingTouch(@NonNull Slider slider) { + if (setup == null) { + return; + } + FloatingLyricsService.baocun(setup); + } + + @Override + public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) { + if (setup == null) { + return; + } + if (slider == binding.slide1) { + setup.size = (int) value; + float v = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, + setup.size, + getResources().getDisplayMetrics()); + binding.lrcView.setTextSize(v); + binding.lrcViewMessage.setTextSize(v - 1.0f); + binding.textSlide1.setText(String.valueOf(setup.size)); + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/view/CardImage.java b/app/src/main/java/com/muqingbfq/view/CardImage.java new file mode 100644 index 0000000..c7c4633 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/view/CardImage.java @@ -0,0 +1,55 @@ +package com.muqingbfq.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.card.MaterialCardView; +import com.muqingbfq.R; + +public class CardImage extends MaterialCardView { + public ImageView imageView; + + public CardImage(Context context) { + super(context); + start(); + } + public CardImage(Context context, AttributeSet attrs) { + super(context, attrs); + start(); + } + + public CardImage(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + start(); + } + + public void start() { + imageView = new ImageView(getContext()); + imageView.setScaleType(ImageView.ScaleType.FIT_XY); + addView(imageView); + imageView.setImageResource(R.drawable.ic_launcher_foreground); + } + + + public void setImage(Object bitmap) { + try { + Glide.with(this) + .load(bitmap) + .error(R.drawable.ic_launcher_foreground) + .into(imageView); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void setImageapply(Object bitmap) { + Glide.with(getContext()) + .load(bitmap) + .apply(new RequestOptions().placeholder(R.drawable.ic_launcher_foreground)) +// .error(R.drawable.app_warning) + .into(imageView); + } +} diff --git a/app/src/main/java/com/muqingbfq/view/Edit.java b/app/src/main/java/com/muqingbfq/view/Edit.java new file mode 100644 index 0000000..de9bf46 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/view/Edit.java @@ -0,0 +1,168 @@ +package com.muqingbfq.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.RippleDrawable; +import android.text.Editable; +import android.text.NoCopySpan; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + +import com.google.android.material.textfield.TextInputLayout; +import com.muqingbfq.R; + +public class Edit extends LinearLayout { + public Edit(@NonNull Context context) { + super(context); + initView(); + } + + AttributeSet attrs; + public Edit(Context context, AttributeSet attrs) { + super(context, attrs); + this.attrs=attrs; + initView(); + } + + public Edit(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.attrs=attrs; + initView(); + } + + ImageView chahao; + public EditText editText; + + private void initView() { + TextInputLayout layout = new TextInputLayout(getContext()); +// layout.clearIconDrawable + setGravity(Gravity.CENTER_VERTICAL); + setBackgroundResource(R.drawable.ui_editview); + setPadding(30, 0, 30, 0); + //构建编辑框 + editText = new EditText(getContext()); + editText.setHint("搜索"); + editText.setSingleLine(true); + editText.setBackground(null); + editText.setTransitionName("edit"); + Drawable startIcon = ContextCompat.getDrawable(getContext(), R.drawable.sousuo); + startIcon.setTint(ContextCompat.getColor(getContext(), + R.color.text_tm)); + Drawable endIcon = ContextCompat.getDrawable(getContext(), R.drawable.chahao); + editText.setCompoundDrawablesRelativeWithIntrinsicBounds(startIcon, null, endIcon, null); + editText.setImeOptions(EditorInfo.IME_ACTION_SEARCH); +// editText.passwordToggleEnabled + addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence var1, int var2, int var3, int var4) { + } + @Override + public void onTextChanged(CharSequence var1, int var2, int var3, int var4) { + iskong(); + } + @Override + public void afterTextChanged(Editable var1) { + } + }); + + ImageView imageView = new ImageView(getContext()); + imageView.setImageResource(R.drawable.sousuo); + addView(imageView, (int) TypedValue.applyDimension(TypedValue. + COMPLEX_UNIT_DIP, 26, getResources().getDisplayMetrics()) + , (int) TypedValue.applyDimension(TypedValue. + COMPLEX_UNIT_DIP, 26, getResources().getDisplayMetrics())); + LayoutParams layoutParams = new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + layoutParams.weight = 1; + layoutParams.gravity = Gravity.CENTER; + addView(editText, layoutParams); + + chahao = new ImageView(getContext()); + chahao.setImageResource(R.drawable.chahao); + Drawable rippleDrawable = new RippleDrawable( + ContextCompat.getColorStateList(getContext(),R.color.bj), + chahao.getBackground(), + null + ); + chahao.setBackground(rippleDrawable); + chahao.setOnClickListener(view -> { + editText.setText(""); + view.setVisibility(GONE); + }); + addView(chahao, (int) TypedValue.applyDimension(TypedValue. + COMPLEX_UNIT_DIP, 26, getResources().getDisplayMetrics()) + , (int) TypedValue.applyDimension(TypedValue. + COMPLEX_UNIT_DIP, 26, getResources().getDisplayMetrics())); + iskong(); + if (attrs != null) { + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Edit); + boolean isEnabled = a.getBoolean(R.styleable.Edit_Enabled, true); + if (!isEnabled) { + removeView(imageView); + removeView(chahao); + +// editText.setGravity(Gravity.CENTER); + editText.setFocusable(false); + editText.setFocusableInTouchMode(false); +// editText.setEnabled(false); + editText.setClickable(false); + + } + a.recycle(); + } + } + + public void addTextChangedListener(TextWatcher textWatcher) { + editText.addTextChangedListener(textWatcher); + } + + public void iskong() { + if (editText.getText().toString().isEmpty()) { + chahao.setVisibility(GONE); + } else { + chahao.setVisibility(VISIBLE); + } + } + + @Override + public void setLayoutParams(ViewGroup.LayoutParams params) { +// params.width = ViewGroup.LayoutParams.MATCH_PARENT; + params.height = ViewGroup.LayoutParams.WRAP_CONTENT; + super.setLayoutParams(params); +// setGravity(Gravity.CENTER_VERTICAL); + } + + public void setOnEditorActionListener(TextView.OnEditorActionListener a) { + editText.setOnEditorActionListener(a); + } + + public void setText(String s) { + editText.setText(s); + } + + public Editable getText() { + return editText.getText(); + } + + public interface TextWatcher extends NoCopySpan, android.text.TextWatcher { + void beforeTextChanged(CharSequence var1, int var2, int var3, int var4); + + void onTextChanged(CharSequence var1, int var2, int var3, int var4); + + void afterTextChanged(Editable var1); + } +} diff --git a/app/src/main/java/com/muqingbfq/view/RecyclerViewH.java b/app/src/main/java/com/muqingbfq/view/RecyclerViewH.java new file mode 100644 index 0000000..2ae52da --- /dev/null +++ b/app/src/main/java/com/muqingbfq/view/RecyclerViewH.java @@ -0,0 +1,73 @@ +package com.muqingbfq.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +public class RecyclerViewH extends RecyclerView { + public RecyclerViewH(@NonNull Context context) { + super(context); + } + + public RecyclerViewH(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public RecyclerViewH(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } +// private boolean disallowIntercept = false; + + private int startX = 0; + private int startY = 0; + boolean isDispatch = true; + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (isDispatch) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + startX = (int) ev.getX(); + startY = (int) ev.getY(); + getParent().requestDisallowInterceptTouchEvent(true); + break; + case MotionEvent.ACTION_MOVE: + int endX = (int) ev.getX(); + int endY = (int) ev.getY(); + int disX = Math.abs(endX - startX); + int disY = Math.abs(endY - startY); + if (Math.abs(disY) > ViewConfiguration.get(getContext()).getScaledTouchSlop()) { + // 当前手指移动距离大于系统认定的最小滚动距离时,不允许父容器拦截触摸事件 + getParent().requestDisallowInterceptTouchEvent(true); + } else { + break; + } + /* + if (disX > disY) { + //为了解决RecyclerView嵌套RecyclerView时横向滑动的问题 + if (disallowIntercept) { + getParent().requestDisallowInterceptTouchEvent(disallowIntercept); + } else { + getParent().requestDisallowInterceptTouchEvent(canScrollHorizontally(startX - endX)); + } + } else { + getParent().requestDisallowInterceptTouchEvent(true); + }*/ + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + getParent().requestDisallowInterceptTouchEvent(false); + break; + } + } + return super.dispatchTouchEvent(ev); + } + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + +} diff --git a/app/src/main/java/com/muqingbfq/view/Text.java b/app/src/main/java/com/muqingbfq/view/Text.java new file mode 100644 index 0000000..652831f --- /dev/null +++ b/app/src/main/java/com/muqingbfq/view/Text.java @@ -0,0 +1,36 @@ +package com.muqingbfq.view; + +import android.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; + +public class Text extends androidx.appcompat.widget.AppCompatTextView { + public Text(@NonNull Context context) { + super(context); + initView(); + } + + + public Text(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + } + + public Text(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(); + } + + private void initView() { + this.setEllipsize(TextUtils.TruncateAt.MARQUEE); + this.setSingleLine(true); + this.setMarqueeRepeatLimit(-1); + } + + @Override + public boolean isFocused() { + return true; + } +} diff --git a/app/src/main/java/com/muqingbfq/view/Toolbar.java b/app/src/main/java/com/muqingbfq/view/Toolbar.java new file mode 100644 index 0000000..2ec67c8 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/view/Toolbar.java @@ -0,0 +1,29 @@ +package com.muqingbfq.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.material.appbar.MaterialToolbar; + +public class Toolbar extends MaterialToolbar { + public Toolbar(@NonNull Context context) { + super(context); + } + + public Toolbar(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public Toolbar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + @Override + public boolean onTouchEvent(MotionEvent event) { + // 处理触摸事件逻辑 + return false; + } +} diff --git a/app/src/main/java/com/muqingbfq/yc.java b/app/src/main/java/com/muqingbfq/yc.java new file mode 100644 index 0000000..b7723de --- /dev/null +++ b/app/src/main/java/com/muqingbfq/yc.java @@ -0,0 +1,67 @@ +package com.muqingbfq; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.util.DisplayMetrics; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.muqingbfq.databinding.ActivityYcBinding; +import com.muqingbfq.mq.gj; + +public class yc extends AppCompatActivity { + public Object exception; + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ActivityYcBinding binding = ActivityYcBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + Intent intent = getIntent(); + exception = intent.getStringExtra("e"); + + String deviceModel = Build.MODEL; + String deviceManufacturer = Build.MANUFACTURER; + String osVersion = Build.VERSION.RELEASE; + int sdkVersion = Build.VERSION.SDK_INT; + @SuppressLint("HardwareIds") String deviceId = Settings.Secure.getString(getContentResolver(), + Settings.Secure.ANDROID_ID); + DisplayMetrics metrics = getResources().getDisplayMetrics(); + int widthPixels = metrics.widthPixels; + int heightPixels = metrics.heightPixels; + float density = metrics.density; + int densityDpi = metrics.densityDpi; +// 假设你已经获取到了手机信息,保存在相应的变量中 + String wb = "设备型号:" + deviceModel + "\n" + + "制造商:" + deviceManufacturer + "\n" + + "设备ID:" + deviceId + "\n" + + "操作系统版本:" + osVersion + "\n" + + "SDK版本:" + sdkVersion + "\n" + + "屏幕尺寸:" + widthPixels + "x" + heightPixels + "\n" + + "屏幕密度:" + density + "\n" + + "密度DPI:" + densityDpi + "\n" + + "异常信息: " + exception.toString(); + binding.text.setText(wb); + binding.button2.setOnClickListener(view -> finish()); + } + public static void start(Context context, Object e) { + gj.sc(e); + Intent intent = new Intent(context, yc.class); + intent.putExtra("e",e.toString()); + context.startActivity(intent); + } + + public static void tc(Context context, Object exception) { + new MaterialAlertDialogBuilder(context) + .setTitle("不是特别重要的警告") + .setMessage(exception.toString()) + .setNegativeButton("无视", null) + .setPositiveButton("分享", null) + .show(); + } +} diff --git a/app/src/main/res/drawable/abc_ic_menu_share_mtrl_alpha.xml b/app/src/main/res/drawable/abc_ic_menu_share_mtrl_alpha.xml new file mode 100644 index 0000000..fc7932f --- /dev/null +++ b/app/src/main/res/drawable/abc_ic_menu_share_mtrl_alpha.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/api.xml b/app/src/main/res/drawable/api.xml new file mode 100644 index 0000000..1f75565 --- /dev/null +++ b/app/src/main/res/drawable/api.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/app/src/main/res/drawable/app_warning.xml b/app/src/main/res/drawable/app_warning.xml new file mode 100644 index 0000000..35b9685 --- /dev/null +++ b/app/src/main/res/drawable/app_warning.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/background.xml b/app/src/main/res/drawable/background.xml new file mode 100644 index 0000000..6f0d1e5 --- /dev/null +++ b/app/src/main/res/drawable/background.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bf.xml b/app/src/main/res/drawable/bf.xml new file mode 100644 index 0000000..39a973e --- /dev/null +++ b/app/src/main/res/drawable/bf.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chahao.xml b/app/src/main/res/drawable/chahao.xml new file mode 100644 index 0000000..6798919 --- /dev/null +++ b/app/src/main/res/drawable/chahao.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/delete.xml b/app/src/main/res/drawable/delete.xml new file mode 100644 index 0000000..f2255da --- /dev/null +++ b/app/src/main/res/drawable/delete.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download.xml b/app/src/main/res/drawable/download.xml new file mode 100644 index 0000000..befd834 --- /dev/null +++ b/app/src/main/res/drawable/download.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/end.xml b/app/src/main/res/drawable/end.xml new file mode 100644 index 0000000..cb35d80 --- /dev/null +++ b/app/src/main/res/drawable/end.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/filesearc.xml b/app/src/main/res/drawable/filesearc.xml new file mode 100644 index 0000000..d7a59c2 --- /dev/null +++ b/app/src/main/res/drawable/filesearc.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/foreground.xml b/app/src/main/res/drawable/foreground.xml new file mode 100644 index 0000000..f7e4f41 --- /dev/null +++ b/app/src/main/res/drawable/foreground.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/fuwuzhongxing.xml b/app/src/main/res/drawable/fuwuzhongxing.xml new file mode 100644 index 0000000..d547cd6 --- /dev/null +++ b/app/src/main/res/drawable/fuwuzhongxing.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/gd.xml b/app/src/main/res/drawable/gd.xml new file mode 100644 index 0000000..f83aeca --- /dev/null +++ b/app/src/main/res/drawable/gd.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..539a267 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..fb5ffb9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/main/res/drawable/liaoqun.xml b/app/src/main/res/drawable/liaoqun.xml new file mode 100644 index 0000000..dc489db --- /dev/null +++ b/app/src/main/res/drawable/liaoqun.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/like.xml b/app/src/main/res/drawable/like.xml new file mode 100644 index 0000000..220de45 --- /dev/null +++ b/app/src/main/res/drawable/like.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/lock.xml b/app/src/main/res/drawable/lock.xml new file mode 100644 index 0000000..375bcba --- /dev/null +++ b/app/src/main/res/drawable/lock.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/lrc_play.png b/app/src/main/res/drawable/lrc_play.png new file mode 100644 index 0000000..2513f81 Binary files /dev/null and b/app/src/main/res/drawable/lrc_play.png differ diff --git a/app/src/main/res/drawable/mdialbum.xml b/app/src/main/res/drawable/mdialbum.xml new file mode 100644 index 0000000..f834ad4 --- /dev/null +++ b/app/src/main/res/drawable/mdialbum.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/mdimusicbox.xml b/app/src/main/res/drawable/mdimusicbox.xml new file mode 100644 index 0000000..d813507 --- /dev/null +++ b/app/src/main/res/drawable/mdimusicbox.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/mdipause.xml b/app/src/main/res/drawable/mdipause.xml new file mode 100644 index 0000000..f99fd93 --- /dev/null +++ b/app/src/main/res/drawable/mdipause.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/mdiplaycircle__1_.xml b/app/src/main/res/drawable/mdiplaycircle__1_.xml new file mode 100644 index 0000000..65c01ea --- /dev/null +++ b/app/src/main/res/drawable/mdiplaycircle__1_.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/mpbackground.xml b/app/src/main/res/drawable/mpbackground.xml new file mode 100644 index 0000000..ff1963b --- /dev/null +++ b/app/src/main/res/drawable/mpbackground.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/mplist.xml b/app/src/main/res/drawable/mplist.xml new file mode 100644 index 0000000..ad01467 --- /dev/null +++ b/app/src/main/res/drawable/mplist.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/mt_sj.xml b/app/src/main/res/drawable/mt_sj.xml new file mode 100644 index 0000000..4842075 --- /dev/null +++ b/app/src/main/res/drawable/mt_sj.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/mt_sx.xml b/app/src/main/res/drawable/mt_sx.xml new file mode 100644 index 0000000..e4a18eb --- /dev/null +++ b/app/src/main/res/drawable/mt_sx.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/mt_xh.xml b/app/src/main/res/drawable/mt_xh.xml new file mode 100644 index 0000000..f1cdf15 --- /dev/null +++ b/app/src/main/res/drawable/mt_xh.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/paihangbang.xml b/app/src/main/res/drawable/paihangbang.xml new file mode 100644 index 0000000..c1e5566 --- /dev/null +++ b/app/src/main/res/drawable/paihangbang.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/play.xml b/app/src/main/res/drawable/play.xml new file mode 100644 index 0000000..6ef1df9 --- /dev/null +++ b/app/src/main/res/drawable/play.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/progress.xml b/app/src/main/res/drawable/progress.xml new file mode 100644 index 0000000..370d3ba --- /dev/null +++ b/app/src/main/res/drawable/progress.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/qq.xml b/app/src/main/res/drawable/qq.xml new file mode 100644 index 0000000..b7fda57 --- /dev/null +++ b/app/src/main/res/drawable/qq.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/set_up.xml b/app/src/main/res/drawable/set_up.xml new file mode 100644 index 0000000..88a178d --- /dev/null +++ b/app/src/main/res/drawable/set_up.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/drawable/sousuo.xml b/app/src/main/res/drawable/sousuo.xml new file mode 100644 index 0000000..69327ab --- /dev/null +++ b/app/src/main/res/drawable/sousuo.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/start.xml b/app/src/main/res/drawable/start.xml new file mode 100644 index 0000000..96cc5cd --- /dev/null +++ b/app/src/main/res/drawable/start.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/syq.xml b/app/src/main/res/drawable/syq.xml new file mode 100644 index 0000000..c302a21 --- /dev/null +++ b/app/src/main/res/drawable/syq.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ui_editview.xml b/app/src/main/res/drawable/ui_editview.xml new file mode 100644 index 0000000..d0fde8d --- /dev/null +++ b/app/src/main/res/drawable/ui_editview.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/user.xml b/app/src/main/res/drawable/user.xml new file mode 100644 index 0000000..c30ffde --- /dev/null +++ b/app/src/main/res/drawable/user.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/user_end.xml b/app/src/main/res/drawable/user_end.xml new file mode 100644 index 0000000..01c74c5 --- /dev/null +++ b/app/src/main/res/drawable/user_end.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/xyq.xml b/app/src/main/res/drawable/xyq.xml new file mode 100644 index 0000000..efab7ed --- /dev/null +++ b/app/src/main/res/drawable/xyq.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/zhuye.xml b/app/src/main/res/drawable/zhuye.xml new file mode 100644 index 0000000..96805ed --- /dev/null +++ b/app/src/main/res/drawable/zhuye.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/zt.xml b/app/src/main/res/drawable/zt.xml new file mode 100644 index 0000000..5728f97 --- /dev/null +++ b/app/src/main/res/drawable/zt.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_bfq.xml b/app/src/main/res/layout-land/activity_bfq.xml new file mode 100644 index 0000000..4463594 --- /dev/null +++ b/app/src/main/res/layout-land/activity_bfq.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about_software.xml b/app/src/main/res/layout/activity_about_software.xml new file mode 100644 index 0000000..b48d5e2 --- /dev/null +++ b/app/src/main/res/layout/activity_about_software.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +