commit 756cd6e75c50ff4cca7a560c414121f6ba441d60 Author: muqing <1966944300@qq.com> Date: Wed Jan 15 18:55:44 2025 +0800 从GitHub移植过来 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfc8a39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.idea +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +*.apk \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3c7e1a --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +## 简介 +一个对接网易云的音乐播放器 + +## 截图 +暂无 +## 联系作者 + * QQ:1966944300 + * 邮箱:1966944300@qq.com + +## 后台 + * Github: [网易云音乐 API](https://github.com/Binaryify/NeteaseCloudMusicApi) + +## 关于 +在兴趣的驱动下,写一个`免费`的东西,有欣喜,也还有汗水,希望你喜欢我的作品,同时也能支持一下。 + +## 修改JAR的包 + (主要修改内容MD3化) + com.github.QuadFlask:colorpicker:0.0.15 + # (歌词做全局变量给悬浮窗歌词) + 'com.github.wangchenyan:lrcview:2.2.1' \ 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..4e0cdf4 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,94 @@ +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 34 + defaultConfig { + applicationId "com.muqingbfq" + minSdk 24 + //noinspection ExpiredTargetSdkVersion,OldTargetApi + targetSdk 34 + 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.7.0' + implementation 'com.google.android.material:material:1.10.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 'jp.wasabeef:glide-transformations:4.3.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' + +// 沉浸式状态栏 + // release 版本 + // 将 $lastVersion 替换成上图中的版本号 + implementation 'com.gitee.zackratos:UltimateBarX:v0.8.1' +// api project(path: ':lrcview') + //歌词组件库 + api "com.github.cy745:EaseView:e11c3208a9" + api "androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03" + + implementation "androidx.media3:media3-exoplayer:1.4.0" + implementation "androidx.media3:media3-ui:1.4.0" + implementation "androidx.media3:media3-common:1.4.0" + implementation "androidx.media3:media3-session:1.4.0" + implementation 'androidx.activity:activity:1.9.1' + + implementation "androidx.lifecycle:lifecycle-process:2.6.1" + + + +} \ 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/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..c9b125f --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.muqingbfq", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "2.6.0", + "outputFile": "Cloud_music-release-v2.6.0.apk" + } + ], + "elementType": "File" +} \ 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..376c193 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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..3d9c024 --- /dev/null +++ b/app/src/main/java/com/dirror/lyricviewx/LyricViewX.kt @@ -0,0 +1,1093 @@ +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/HomeSteer.java b/app/src/main/java/com/muqingbfq/HomeSteer.java new file mode 100644 index 0000000..b166b27 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/HomeSteer.java @@ -0,0 +1,123 @@ +package com.muqingbfq; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.text.Editable; +import android.view.View; +import android.widget.Toast; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.muqingbfq.fragment.wode; +import com.muqingbfq.login.user_logs; +import com.muqingbfq.mq.EditViewDialog; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wl; +import com.muqingbfq.view.Edit; + +import org.json.JSONObject; + +public class HomeSteer { + home home; + ActivityResultLauncher dlintent; + + public HomeSteer(home home) { + this.home = home; + dlintent = home.registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + // 处理返回结果 + Intent data = result.getData(); + boolean bool = data.getBooleanExtra("bool", false); + if (bool) { + Yes(); + return; + } + } + One(); + }); + SetIP(); +// One(); + + } + + public void One() { + MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(home); + materialAlertDialogBuilder.setTitle("引导登陆"); + materialAlertDialogBuilder.setItems(new String[]{"游客", "登陆"}, (dialog, which) -> { + if (which == 0) { + new Thread() { + @Override + public void run() { + super.run(); + //获取游客Cookie + String hq = wl.hq("/register/anonimous"); + try { + JSONObject jsonObject = new JSONObject(hq); + wl.setcookie(jsonObject.getString("cookie")); + home.runOnUiThread(() -> Yes()); + } catch (Exception e) { + home.runOnUiThread(() -> Toast.makeText(home, "游客登陆失败:" + e.getMessage(), Toast.LENGTH_SHORT).show()); + gj.sc(e); + } + } + }.start(); + } else if (which == 1) { + dlintent.launch(new Intent(home, user_logs.class)); + } + }); + materialAlertDialogBuilder.show(); + } + + public void Yes() { + + } + + /** + * 设置IP地址 + */ + public void SetIP() { + SharedPreferences nickname = home.getSharedPreferences("Set_up", Context.MODE_PRIVATE); + if (nickname.getString("IP", "").isEmpty()) { + EditViewDialog editViewDialog = new EditViewDialog(home, "IP"); + editViewDialog.setMessage("请输入部署了NeteaseCloudMusicApi的服务器地址,\n例如" + + "https://api.csm.sayqz.com"); +// editViewDialog.setPositive() + editViewDialog.buttonb.setEnabled(false); + editViewDialog.editText.setMaxLines(1); + editViewDialog.editText.addTextChangedListener(new Edit.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) { + //正则表达式检查是否为 https://api.csm.sayqz.com这样的 + editViewDialog.buttonb.setEnabled(var1.toString().matches("^(https?://).+[^/]")); + } + + @Override + public void afterTextChanged(Editable var1) { + + } + }); + editViewDialog.setPositive(v -> { + main.api = editViewDialog.getEditText(); + nickname.edit().putString("IP", editViewDialog.getEditText()).apply(); + One(); + editViewDialog.dismiss(); + }); + editViewDialog.show(); + } else { + main.api = nickname.getString("IP", ""); + One(); + } + } +} 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..ae8b148 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/MP3.java @@ -0,0 +1,56 @@ +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(){} + + + public MP3(String id) { + this.id = id; + } + + 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 + + ",url=" + url + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muqingbfq/PlaybackService.java b/app/src/main/java/com/muqingbfq/PlaybackService.java new file mode 100644 index 0000000..a2f31aa --- /dev/null +++ b/app/src/main/java/com/muqingbfq/PlaybackService.java @@ -0,0 +1,217 @@ +package com.muqingbfq; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media3.common.C; +import androidx.media3.common.MediaItem; +import androidx.media3.common.MediaMetadata; +import androidx.media3.common.PlaybackException; +import androidx.media3.common.Player; +import androidx.media3.common.Tracks; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.session.MediaSession; +import androidx.media3.session.MediaSessionService; + +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.muqingbfq.api.url; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wj; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class PlaybackService extends MediaSessionService { + public static MediaSession mediaSession = null; + public static List list = new ArrayList<>(); + + //历史记录 +// public static final List listHistory = new ArrayList<>(); + + public static void ListSave() { + new Thread(() -> wj.xrwb(wj.filesdri + "list.json", new Gson().toJson(list))).start(); + } + + + public Player.Listener PlayerListener = new Player.Listener() { + @Override + public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { + // 处理 playWhenReady 的变化 + } + + @Override + public void onPlaybackStateChanged(int playbackState) { + // 处理播放状态的变化 + if (playbackState == Player.STATE_ENDED) { + // 检查当前播放的媒体项是否是最后一项 + Player player = mediaSession.getPlayer(); + if (player.getCurrentMediaItemIndex() == player.getMediaItemCount() - 1) { + // 如果是最后一项,回到第一项并播放 + player.seekTo(0,0); + player.play(); + } + } + + } + + @Override + public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) { + // 处理位置不连续变化 + } + + @Override + public void onMediaItemTransition(@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) { + if (mediaItem != null) { + // 输出当前的 MediaItem 信息 + String title = mediaItem.mediaMetadata.title != null ? mediaItem.mediaMetadata.title.toString() : "未知标题"; + String artist = mediaItem.mediaMetadata.artist != null ? mediaItem.mediaMetadata.artist.toString() : "未知艺术家"; + +// bfqkz.lishi_list.removeIf(mp3 -> mp3.id.equals(mediaItem.mediaId)); +// bfqkz.lishi_list.add(0, new MP3(mediaItem.mediaId, title, artist, mediaItem.mediaMetadata.artworkUri.toString())); +// new Thread(() -> wj.xrwb(wj.gd + "mp3_hc.json", new Gson().toJson(bfqkz.lishi_list))).start(); + } + } + @Override + public void onTracksChanged(@Nullable Tracks tracks) { + gj.sc(tracks); + // Update UI using current tracks. + } + + int error_count = 0;//设置错误次数 + MediaItem currentMediaItem = null; + int currentIndex = 0; + + @Override + public void onPlayerError(@NonNull PlaybackException error) { + // 当播放发生错误时调用 + // 如果错误是由于资源找不到 + Player player = mediaSession.getPlayer(); + if (++error_count > 3) { + currentIndex = player.getNextMediaItemIndex(); + if (currentIndex != C.INDEX_UNSET) { + gj.sc("播放失败,已跳过"); + return; + } else { + currentMediaItem = player.getMediaItemAt(currentIndex); // 获取下一首的 MediaItem + } + } else { + currentMediaItem = player.getCurrentMediaItem(); + currentIndex = player.getCurrentMediaItemIndex(); // 获取当前播放项的索引 + } + new Thread(() -> { + MP3 hq = url.hq(new MP3(currentMediaItem.mediaId)); + hq.picurl = url.picurl(hq.id); + // 设置新的 MediaItem + MediaItem newMediaItem = currentMediaItem.buildUpon() + .setUri(hq.url) // 更新 URI + .build(); // 构建新的 MediaItem + main.handler.post(() -> { + player.replaceMediaItem(currentIndex, newMediaItem); + player.prepare(); + player.play(); + error_count = 0; + }); + }).start(); + } + }; + + + @UnstableApi + public void onCreate() { + super.onCreate(); + ExoPlayer player = new ExoPlayer.Builder(this) + .build(); + mediaSession = new MediaSession.Builder(this, player).build(); + + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(new ComponentName(this, home.class));//用ComponentName得到class对象 + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);// 关键的一步,设置启动模式,两种情况 + PendingIntent pendingIntent = PendingIntent.getActivity( + this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + mediaSession.setSessionActivity(pendingIntent); +// 添加 ExoPlayer 监听器 + player.addListener(PlayerListener); + + + String nickname = wj.dqwb(wj.filesdri + "list.json"); + if (!Strings.isNullOrEmpty(nickname)) { + try { + list = new Gson().fromJson(nickname, new com.google.gson.reflect.TypeToken>() { + }.getType()); + for (MP3 mediaItem : list) { + player.addMediaItem(PlaybackService.GetMp3(mediaItem)); + } + } catch (Exception e) { + gj.sc(e); + list = new ArrayList<>(); + wj.sc(wj.filesdri + "list.json"); + } + } + + } + + @Nullable + @Override + public MediaSession onGetSession(@NonNull MediaSession.ControllerInfo controllerInfo) { + return mediaSession; + } + + @Override + public void onDestroy() { + mediaSession.getPlayer().release(); + mediaSession.release(); + mediaSession = null; + super.onDestroy(); + } + + public static MediaItem GetMp3(MP3 mp3) { + // 创建媒体的元数据(如标题、描述、图片等) + MediaMetadata mediaMetadata = new MediaMetadata.Builder() + .setTitle(mp3.name) + .setArtist(mp3.zz) + .setAlbumTitle(mp3.zz) + .setArtworkUri(Uri.parse(mp3.picurl)) // 图片URL + .build(); +// 创建带有元数据的 MediaItem + + return new MediaItem.Builder() + .setMediaId(mp3.id) // 设置媒体的唯一ID + .setUri(Strings.isNullOrEmpty(mp3.url) ? "" : mp3.url) + .setMediaMetadata(mediaMetadata) // 将元数据添加到 MediaItem + .build(); + } + + public static void AddMediaItem(MP3 mp3) { + if (mediaSession != null) { + new Thread(() -> { + MediaItem mediaItem = GetMp3(mp3); + main.handler.post(() -> { + Player player = mediaSession.getPlayer(); + for (int i = 0; i < player.getMediaItemCount(); i++) { + MediaItem existingItem = player.getMediaItemAt(i); + if (Objects.equals(existingItem.mediaId, mp3.id)) { + player.seekTo(i, 0); + return; + } + } + player.addMediaItem(0, mediaItem); + player.seekTo(0, 0); + player.prepare(); + player.play(); + }); + }).start(); + } + + } + + +} \ No newline at end of file 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/Music.java b/app/src/main/java/com/muqingbfq/activity/Music.java new file mode 100644 index 0000000..cacbd87 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/activity/Music.java @@ -0,0 +1,544 @@ +package com.muqingbfq.activity; + +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.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.net.Uri; +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.WindowManager; +import android.view.animation.DecelerateInterpolator; +import android.widget.SeekBar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.media3.common.MediaItem; +import androidx.media3.common.MediaMetadata; +import androidx.media3.common.Player; +import androidx.media3.common.util.Util; +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.muqingbfq.MP3; +import com.muqingbfq.PlaybackService; +import com.muqingbfq.R; +import com.muqingbfq.bfq_an; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.ActivityMusicBinding; +import com.muqingbfq.fragment.Media; +import com.muqingbfq.main; +import com.muqingbfq.mq.AppCompatActivity; +import com.muqingbfq.mq.MusicViewModel; +import com.muqingbfq.mq.gj; + + +public class Music extends AppCompatActivity implements GestureDetector.OnGestureListener { + + private Player player = PlaybackService.mediaSession.getPlayer(); + private int TdtHeight = 15; + public static Bitmap backgroundbitmap=null; + + public static void startActivity(Context context, MP3 mp3) { + Intent intent = new Intent(context, Music.class); + intent.putExtra("MP3", mp3); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + context.startActivity(intent); + } + + public static void startActivity(Context context) { + Intent intent = new Intent(context, Music.class); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + context.startActivity(intent); + } + + DisplayMetrics displayMetrics = new DisplayMetrics(); + + GestureDetector gestureDetector; + float Minfloat = 1000f; + + @SuppressLint("ClickableViewAccessibility") + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + gestureDetector = new GestureDetector(this, this); + setContentView(); + color(backgroundbitmap); + + // 获取屏幕的高度 + WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + windowManager.getDefaultDisplay().getMetrics(displayMetrics); + ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); +// 创建新的MarginLayoutParams + ViewGroup.LayoutParams layoutParams = binding.toolbar.getLayoutParams(); +// 设置margin + ((ViewGroup.MarginLayoutParams) layoutParams).setMargins(0, systemBars.top, 0, 0); + binding.toolbar.setLayoutParams(layoutParams); + if (gj.isTablet(this)) { +// binding.image2.setLayoutParams(layoutParams); + ViewGroup.LayoutParams layoutParams1 = binding.cardview.getLayoutParams(); + layoutParams1.width = displayMetrics.heightPixels / 2; + layoutParams1.height = displayMetrics.heightPixels / 2; + + } + return insets; + }); + + Minfloat = displayMetrics.heightPixels - displayMetrics.heightPixels / 3f; + binding.kg.setOnClickListener(view -> Util.handlePlayPauseButtonAction(player)); + binding.xyq.setOnClickListener(v -> { + boolean b = player.hasNextMediaItem(); + if (b) { + player.seekToNextMediaItem(); + } else { + gj.ts(v.getContext(), "已经是最后一首了"); + } + }); + binding.syq.setOnClickListener(v -> { + boolean b = player.hasPreviousMediaItem(); + if (b) { + player.seekToPreviousMediaItem(); + } else { + gj.ts(v.getContext(), "已经是第一首了"); + } + }); + player.addListener(Listener); + if (PlaybackService.mediaSession != null) { + updateUI(player); + } + binding.tdt.post(() -> TdtHeight = binding.tdt.getHeight()); + binding.tdt.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + long actualPosition = (progress * player.getDuration()) / 100; + String time = bfq_an.getTime(actualPosition); + binding.timeB.setText(time); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // 获取 View 当前的高度; + // 创建一个 ValueAnimator,从当前高度逐渐增加到目标高度 + ValueAnimator animator = ValueAnimator.ofInt(TdtHeight, TdtHeight + 10); + animator.setDuration(300); // 设置动画持续时间为 300 毫秒 + animator.setInterpolator(new DecelerateInterpolator()); // 设置动画插值器 + // 在动画过程中更新 View 的高度 + animator.addUpdateListener(animation -> { + // 获取当前动画的值(高度) + int animatedValue = (int) animation.getAnimatedValue(); + + // 更新 View 的高度 + ViewGroup.LayoutParams layoutParams = seekBar.getLayoutParams(); + layoutParams.height = animatedValue; + seekBar.setLayoutParams(layoutParams); + }); + + // 开始动画 + animator.start(); + isDrag = true; + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + isDrag = false; + int progress = seekBar.getProgress(); + if (progress >= 100) { + player.seekToNextMediaItem(); + } else { + + long actualPosition = (progress * player.getDuration()) / 100; + player.seekTo(actualPosition); + } + + + ValueAnimator animator = ValueAnimator.ofInt(TdtHeight, TdtHeight - 10); + animator.setDuration(300); // 设置动画持续时间为 300 毫秒 + animator.setInterpolator(new DecelerateInterpolator()); // 设置动画插值器 + // 在动画过程中更新 View 的高度 + animator.addUpdateListener(animation -> { + // 获取当前动画的值(高度) + int animatedValue = (int) animation.getAnimatedValue(); + // 更新 View 的高度 + ViewGroup.LayoutParams layoutParams = seekBar.getLayoutParams(); + layoutParams.height = animatedValue; + seekBar.setLayoutParams(layoutParams); + }); + + // 开始动画 + animator.start(); + } + }); + binding.back.setOnClickListener(v -> finish()); + + binding.fragmentBfq.setOnClickListener(v -> { + if (binding.cardview.getVisibility() == View.VISIBLE) { + binding.cardview.setVisibility(View.GONE); + binding.lrcView.setVisibility(View.VISIBLE); + } else { + binding.cardview.setVisibility(View.VISIBLE); + binding.lrcView.setVisibility(View.GONE); + } + }); + 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 -> { + player.seekTo(time); + return false; + }); + + binding.lrcView.setOnSingerClickListener(() -> switchViews(binding.lrcView, binding.cardview)); + + 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; + }); + + //播放列表 + binding.bfqListMp3.setOnClickListener(v -> com.muqingbfq.fragment.bflb_db.start(v.getContext())); + + } + + //是否拖动 + private boolean isDrag = false; + Runnable runnable = new Runnable() { + @Override + public void run() { + if (player != null && !isDrag) { + // 获取当前进度和持续时间 + long currentPosition = player.getCurrentPosition(); + long duration = player.getDuration(); + + // 更新进度条(假设有一个 SeekBar 名为 seekBar) + if (duration > 0) { + int progress = (int) ((currentPosition * 100) / duration); +// gj.sc(progress); + if (progress < 1) { + progress = 1; + } + binding.tdt.setProgress(progress); +// binding.tdt.setMax(100); + binding.lrcView.updateTime(currentPosition, true); + binding.timeA.setText(bfq_an.getTime(duration)); + binding.timeB.setText(bfq_an.getTime(currentPosition)); + } + } + // 计划下一次更新 + main.handler.postDelayed(this, 1000); // 每秒更新一次 + } + }; + + @Override + public void finish() { + super.finish(); + player.removeListener(Listener); + } + + private Player.Listener Listener = new Player.Listener() { + @Override + public void onEvents(@NonNull Player player, @NonNull Player.Events events) { + // 监听播放状态变化 + if (events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) { + boolean isPlaying = player.getPlayWhenReady(); + if (isPlaying) { + gj.sc("正在播放"); + } else { + gj.sc("播放暂停"); + } + } + + // 监听下一曲事件 + if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) { + MediaItem currentMediaItem = player.getCurrentMediaItem(); + if (currentMediaItem != null) { + gj.sc("播放下一曲: "); + } + } + + // 监听上一曲事件(通常通过手动调用控制播放器的skipToPrevious方法实现) + if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) { + // 你的逻辑代码,处理上一曲 + gj.sc("播放上一曲"); + } + + if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) { + int playbackState = player.getPlaybackState(); + + switch (playbackState) { + case Player.STATE_READY: + if (player.getPlayWhenReady()) { + if (bfqkz.lrc != null) { + String[] strings = Media.loadLyric(); + binding.lrcView.loadLyric(strings[0], strings[1]); + } + + gj.sc("播放开始"); + } + break; + case Player.STATE_ENDED: + gj.sc("播放结束"); + break; + default: + // 处理其他状态 + break; + } + } + updateUI(player); + + } + }; + + @Override + protected ActivityMusicBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityMusicBinding.inflate(layoutInflater); + } + + @Override + protected void onResume() { + super.onResume(); + main.handler.post(runnable); + } + + @Override + protected void onPause() { + super.onPause(); + // 取消所有未完成的任务 + main.handler.removeCallbacks(runnable); + } + + + private void updateUI(Player player) { + boolean shouldShowPlayButton = Util.shouldShowPlayButton(player); + binding.kg.setImageResource(shouldShowPlayButton ? R.drawable.zt : R.drawable.bf); + // 获取当前播放的 MediaItem + MediaItem currentMediaItem = player.getCurrentMediaItem(); + if (currentMediaItem != null && !isFinishing() && !isDestroyed()) { + MediaMetadata metadata = currentMediaItem.mediaMetadata; + String title = metadata.title != null ? metadata.title.toString() : "没有名字的音乐?"; + String artist = metadata.artist != null ? metadata.artist.toString() : "未知艺术家"; + binding.name.setText(title); + binding.zz.setText(artist); + SetBackGround(metadata.artworkUri); + } + + } + + + private void SetBackGround(Uri artworkUri){ + Glide.with(this) + .asBitmap() + .load(artworkUri) + .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 true; + } + + @Override + public boolean onResourceReady(@NonNull Bitmap resource, + @NonNull Object model, Target target, + @NonNull DataSource dataSource, + boolean isFirstResource) { + color(resource); + backgroundbitmap = resource; + binding.cardview.imageView.setImageBitmap(resource); + return true; + } + }).into(binding.cardview.imageView); + } + + + private void color(Bitmap bitmap) { + if (bitmap == null) { + return; + } + 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 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); + } + + //触摸 +// public boolean onTouchEvent(MotionEvent event) + + + @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; + } + + boolean isswitchViews = false;//是否在执行中 + + private void switchViews(final View view1, final View view2) { + // 隐藏view1并显示view2的动画效果 + if (isswitchViews || gj.isTablet(this)) { + return; + } + isswitchViews = true; + if (view2.getId() == binding.lrcView.getId()) { + binding.lrcView.updateTime(player.getCurrentPosition(), true); + } + 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(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + isswitchViews = false; + } + }); + } + }); + } + + // 判断触摸点是否在视图范围内的辅助方法 + @Override + public boolean onScroll(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(MotionEvent e1, @NonNull MotionEvent e2, + float velocityX, float velocityY) { + return false; + } +} 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..ad914e6 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/activity_about_software.java @@ -0,0 +1,169 @@ +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.FragmentActivity; +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 FragmentActivity { + @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 -> { + }); + 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); + } + viewById.setText(Html.fromHtml(stringBuilder.toString(), Html.FROM_HTML_MODE_LEGACY)); + open.close(); + bufferedReader.close(); + + } catch (IOException e) { + 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..251cf9b --- /dev/null +++ b/app/src/main/java/com/muqingbfq/activity_search.java @@ -0,0 +1,345 @@ +package com.muqingbfq; + +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.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.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.databinding.ViewSearchItemBinding; +import com.muqingbfq.fragment.search; +import com.muqingbfq.mq.FragmentActivity; +import com.muqingbfq.mq.VH; +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.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()); + } + + @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 search_adapter(list, this::start)); + //设置项点击监听 + 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; + }); + } + + @FunctionalInterface + public interface TaskAction { + void execute(T t); + } + + public static class search_adapter extends RecyclerView.Adapter> { + public List list; + TaskAction taskAction; + + public search_adapter(List list, TaskAction taskAction) { + this.taskAction = taskAction; + this.list = list; + } + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH<>(ViewSearchItemBinding.inflate(LayoutInflater.from(parent.getContext()), 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 -> taskAction.execute(s)); + } + + @Override + public int getItemCount() { + return list.size(); + } + + } + + public void dismiss() { + binding.searchview.hide(); + } + + public static void addSearchRecord(String name,List json_list,SearchRecordAdapter adapter) { + try { + int existingIndex = json_list.indexOf(name); + if (existingIndex != -1) { + // 交换两个元素的位置 + json_list.remove(name); + json_list.add(0, name); + adapter.notifyItemMoved(existingIndex, 0); + } else { +// json_list.remove(name); + json_list.add(0, name); + adapter.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); +// addSearchRecordd(name); + } + } + + public static class SearchRecordAdapter extends RecyclerView.Adapter> { + SearchView searchView; + public List json_list = new ArrayList<>(); + + public SearchRecordAdapter(SearchView searchView) { + this.searchView = searchView; + 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); + } + } + gj.sc(json_list.size()); +// 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 VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH<>(ListTextBinding.inflate(LayoutInflater.from(parent.getContext()), + parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + String keyword = json_list.get(position); + holder.binding.getRoot().setText(keyword); + holder.binding.getRoot().setOnClickListener(v -> { + searchView.setText(keyword); +// start(keyword); + }); + holder.binding.getRoot().setOnCloseIconClickListener(view -> { + json_list.remove(keyword); + notifyItemRemoved(holder.getBindingAdapterPosition()); + wj.xrwb(wj.filesdri + wj.lishi_json, new Gson().toJson(json_list)); + }); + } + + @Override + public int getItemCount() { + return json_list.size(); + } + } + +// @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/AdapterGd.java b/app/src/main/java/com/muqingbfq/adapter/AdapterGd.java new file mode 100644 index 0000000..6fdae57 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/adapter/AdapterGd.java @@ -0,0 +1,133 @@ +package com.muqingbfq.adapter; + +import android.annotation.SuppressLint; +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.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.media3.common.MediaItem; +import androidx.media3.common.Player; +import androidx.palette.graphics.Palette; +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.muqingbfq.MP3; +import com.muqingbfq.PlaybackService; +import com.muqingbfq.R; +import com.muqingbfq.XM; +import com.muqingbfq.api.playlist; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.ListGdBinding; +import com.muqingbfq.fragment.gd; +import com.muqingbfq.fragment.mp3; +import com.muqingbfq.main; +import com.muqingbfq.mq.VH; +import com.muqingbfq.mq.gj; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class AdapterGd extends RecyclerView.Adapter> { + + public List list = new ArrayList<>(); + + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH<>(ListGdBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + XM xm = list.get(position); + holder.itemView.setOnClickListener(v -> { + mp3.drawable = holder.binding.image.getDrawable(); + mp3.start(v.getContext(), new String[]{xm.id, xm.name}); + }); + 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 true; + } + + @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.binding.text1.setText(xm.name); + holder.binding.kg.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new Thread() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + super.run(); + List an = playlist.hq(xm.id); + if (bfqkz.ms == 2) { + Collections.shuffle(bfqkz.list); + } + + main.handler.post(() -> { + if (PlaybackService.mediaSession == null) { + return; + } + Player player = PlaybackService.mediaSession.getPlayer(); + player.clearMediaItems(); + for (MP3 mp3 : an) { +// gj.sc(mp3.url); + MediaItem mediaItem = PlaybackService.GetMp3(mp3); + player.addMediaItem(mediaItem); + } + player.prepare(); + player.seekTo(0,0); + player.play(); + //保存播放列表 + PlaybackService.list.clear(); + PlaybackService.list.addAll(an); + PlaybackService.ListSave(); + }); + } + }.start(); + } + }); + } + + @Override + public int getItemCount() { + return list.size(); + } +} diff --git a/app/src/main/java/com/muqingbfq/adapter/AdapterGdH.java b/app/src/main/java/com/muqingbfq/adapter/AdapterGdH.java new file mode 100644 index 0000000..247d3f3 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/adapter/AdapterGdH.java @@ -0,0 +1,96 @@ +package com.muqingbfq.adapter; + +import android.annotation.SuppressLint; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media3.common.MediaItem; +import androidx.media3.common.Player; +import androidx.palette.graphics.Palette; +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.target.Target; +import com.muqingbfq.MP3; +import com.muqingbfq.PlaybackService; +import com.muqingbfq.XM; +import com.muqingbfq.api.playlist; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.ListGdBBinding; +import com.muqingbfq.databinding.ListGdBinding; +import com.muqingbfq.fragment.mp3; +import com.muqingbfq.main; +import com.muqingbfq.mq.VH; +import com.muqingbfq.mq.gj; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class AdapterGdH extends RecyclerView.Adapter> { + + public List list = new ArrayList<>(); + + public AdapterGdH() {} + public AdapterGdH(List list) { + this.list = list; + } + + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH<>(ListGdBBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + XM xm = list.get(position); + holder.binding.text1.setText(xm.name); + holder.binding.text2.setText(xm.message); + gj.sc(xm.picurl); + 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 true; + } + + @Override + public boolean onResourceReady(@NonNull Bitmap resource, + @NonNull Object model, Target target, + @NonNull DataSource dataSource, boolean isFirstResource) { + Palette.from(resource).generate(palette -> { + Palette.Swatch lightVibrantSwatch = palette.getVibrantSwatch(); + if (lightVibrantSwatch != null) { + int color = lightVibrantSwatch.getRgb(); + holder.binding.text1.setTextColor(color); + holder.binding.getRoot().setRippleColor(ColorStateList.valueOf(color)); + } + }); + holder.binding.image.setImageBitmap(resource); + return true; + } + }) + .into(holder.binding.image); + holder.binding.getRoot().setOnClickListener(v -> mp3.start(v.getContext(), new String[]{xm.id, xm.name})); + } + + @Override + public int getItemCount() { + return list.size(); + } +} 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..b0c6368 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/adapter/AdapterMp3.java @@ -0,0 +1,123 @@ +package com.muqingbfq.adapter; + +import android.annotation.SuppressLint; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.media3.common.MediaItem; +import androidx.media3.common.Player; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.google.common.base.Strings; +import com.muqingbfq.MP3; +import com.muqingbfq.PlaybackService; +import com.muqingbfq.R; +import com.muqingbfq.api.url; +import com.muqingbfq.databinding.ListMp3ImageBinding; +import com.muqingbfq.main; +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<>(); + + public AdapterMp3() { + + } + + public AdapterMp3(List list) { + this.list = list; + } + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH<>(ListMp3ImageBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); + } + + @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); + int themeColor = gj.getThemeColor(holder.itemView.getContext(), com.google.android.material.R.attr.colorSurface); + holder.binding.getRoot().setCardBackgroundColor(themeColor); + if (PlaybackService.mediaSession != null) { + Player player = PlaybackService.mediaSession.getPlayer(); + MediaItem currentMediaItem = player.getCurrentMediaItem(); + if (currentMediaItem != null) { + String mediaId = currentMediaItem.mediaId; + if (mediaId.equals(x.id)) { +// holder.binding.getRoot() + holder.binding.getRoot().setCardBackgroundColor( + gj.getThemeColor(holder.itemView.getContext(), com.google.android.material.R.attr.colorSurfaceVariant)); + } + } + } + + if (Strings.isNullOrEmpty(x.picurl)) { + holder.binding.text1.setText(String.valueOf(position + 1)); + holder.binding.imageView.setVisibility(ViewGroup.GONE); + holder.binding.linsum.setVisibility(View.VISIBLE); + }else{ + Glide.with(holder.itemView.getContext()).load(list.get(position).picurl) + .apply(new RequestOptions().placeholder(R.drawable.ic_launcher_foreground)) + .error(R.drawable.ic_launcher_foreground) + .into(holder.binding.imageView); + } + + holder.itemView.setOnClickListener(view -> { + if (PlaybackService.mediaSession == null) { + return; + } + Player player = PlaybackService.mediaSession.getPlayer(); + new Thread() { + @Override + public void run() { + super.run(); + MP3 hq = url.hq(x); +// gj.sc(String.format("链接:%s,图片:%s", hq.url,hq.picurl)); + main.handler.post(() -> { + for (int i = 0; i < player.getMediaItemCount(); i++) { + MediaItem currentItem = player.getMediaItemAt(i); + if (currentItem.mediaId.equals(hq.id)) { + gj.sc("存在播放:" + currentItem.mediaId + "==" + hq.id + " i=" + i); + player.seekTo(i,0); + player.prepare(); + player.play(); + notifyDataSetChanged(); + return; + } + } + gj.sc("不存在添加播放"); + PlaybackService.list.add(hq); + PlaybackService.ListSave(); + MediaItem mediaItem = PlaybackService.GetMp3(hq); + player.addMediaItem(0,mediaItem); + player.seekTo(0,0); + player.prepare(); + player.play(); + notifyDataSetChanged(); + }); + } + }.start(); + + }); + } + + @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..4f4e0a5 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/api/playlist.java @@ -0,0 +1,224 @@ +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.ArrayList; +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 List hq(String uid) { + List list = new ArrayList<>(); + try { + String hq = wj.dqwb(wj.gd + uid); + if (hq == null || hq.isEmpty()) { + hq = gethq(uid); + } + 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 list; + } catch (Exception e) { + gj.sc("失败的错误 " + e); + } + return list; + } + + 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..4df3aa6 --- /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); + MP3 mp3 = new MP3(); + mp3.name = x.name; + mp3.id = x.id; + mp3.picurl = x.picurl; + mp3.zz = x.zz; + mp3.url = x.url; + getLrc(x.id); + Media.loadLyric(); + try { + if (wj.cz(mp3.id)) { + mp3.url = mp3.id; + return mp3; + } else if (wj.cz(wj.mp3 + mp3.id)) { + mp3.url = wj.mp3 + mp3.id; + return mp3; + } else if (wj.cz(wj.filesdri + "hc/" + mp3.id)) { + mp3.url = wj.filesdri + "hc/" + mp3.id; + return mp3; + } + String level = "standard"; + boolean wiFiConnected = gj.isWiFiConnected(); + if (wiFiConnected) { + level = "exhigh"; + } + String hq = wl.hq(api + "?id=" + mp3.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")); + mp3.url = jsonObject.getString("url"); + mp3.picurl = picurl(mp3.id); + return mp3; + } catch (JSONException e) { + gj.sc("url hq :" + e); + } + return null; + } + + 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_an.java b/app/src/main/java/com/muqingbfq/bfq_an.java new file mode 100644 index 0000000..9b33813 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/bfq_an.java @@ -0,0 +1,60 @@ +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 { + 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..6174388 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/bfqkz.java @@ -0,0 +1,93 @@ +package com.muqingbfq; + +import android.annotation.SuppressLint; +import android.app.PendingIntent; +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.gj; + +import java.util.ArrayList; +import java.util.List; + +public class bfqkz extends MediaBrowserServiceCompat { + + 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 int getmti() { + if (xm == null) { + return 0; + } + int i = bfqkz.list.indexOf(xm) + 1; + if (i >= bfqkz.list.size()) { + i = 0; + } + return i; + } + 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); +// 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); + } + + @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..852b341 --- /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..45bdb73 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/bflb_db.java @@ -0,0 +1,230 @@ +package com.muqingbfq.fragment; + +import static android.content.Context.WINDOW_SERVICE; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.media3.common.MediaItem; +import androidx.media3.common.Player; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.muqingbfq.PlaybackService; +import com.muqingbfq.R; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.FragmentBflbDbBinding; +import com.muqingbfq.databinding.ListMp3ABinding; +import com.muqingbfq.list.MyViewHoder; +import com.muqingbfq.main; +import com.muqingbfq.yc; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class bflb_db extends BottomSheetDialog { + public static RecyclerView.Adapter adapter; + FragmentBflbDbBinding binding; + + private void ingList() { + + if (PlaybackService.mediaSession == null) { + return; + } + Player player = PlaybackService.mediaSession.getPlayer(); + List list = new ArrayList<>(); + int mediaItemCount = player.getMediaItemCount(); + for (int i = 0; i < mediaItemCount; i++) { + list.add(player.getMediaItemAt(i)); + } + binding.lb.setAdapter(new spq(list)); + } + + @Override + public void onStart() { + super.onStart(); + // 获取底部弹窗的根视图 + View view = findViewById(com.google.android.material.R.id.design_bottom_sheet); + if (view != null) { + // 设置宽度 + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + layoutParams.width = (int) (getContext().getResources().getDisplayMetrics().widthPixels * 0.6); // 占屏幕宽度的 60% + view.setLayoutParams(layoutParams); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = FragmentBflbDbBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + // 设置高度 +// DisplayMetrics displayMetrics = new DisplayMetrics(); +// WindowManager windowManager = (WindowManager) getContext().getSystemService(WINDOW_SERVICE); +// windowManager.getDefaultDisplay().getMetrics(displayMetrics); +// binding.getRoot().getLayoutParams().height = displayMetrics.heightPixels - displayMetrics.heightPixels / 3; +// binding.getRoot().requestLayout(); + + +// 设置固定的高度(例如 500dp) + try { + ingList(); + 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.getAbsoluteAdapterPosition(); + int toPosition = target.getAbsoluteAdapterPosition(); + // 在这里处理数据集的移动 +// Collections.swap(bfqkz.list, fromPosition, toPosition); + Player player = PlaybackService.mediaSession.getPlayer(); + player.moveMediaItem(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() { + if (PlaybackService.mediaSession == null) { + return 0; + } + Player player = PlaybackService.mediaSession.getPlayer(); + int i = player.getCurrentMediaItemIndex(); + if (i == -1) { + i = 0; + } + return i; + } + + public bflb_db(Context context) { + super(context); + } + + public static void start(Context context) { + bflb_db dialog = new bflb_db(context); + dialog.setOnShowListener(dialogInterface -> { + BottomSheetDialog d = (BottomSheetDialog) dialogInterface; + FrameLayout bottomSheet = d.findViewById(com.google.android.material.R.id.design_bottom_sheet); + if (bottomSheet != null) { + BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet); + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); // 默认展开 + behavior.setSkipCollapsed(true); // 禁止折叠 + } + }); + dialog.show(); + } + + private class spq extends RecyclerView.Adapter { + List list; + + public spq(List list) { + this.list = list; + adapter = this; + } + + @NonNull + @Override + public MyViewHoder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new MyViewHoder(ListMp3ABinding. + inflate(getLayoutInflater(), parent, false)); + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public void onBindViewHolder(@NonNull MyViewHoder holder, int position) { + MediaItem mediaItem = list.get(position); + holder.bindingA.name.setText(mediaItem.mediaMetadata.title); + holder.bindingA.zz.setText(String.format(" · %s", mediaItem.mediaMetadata.artist)); + int color = ContextCompat.getColor(holder.getContext(), R.color.text); + //获取当前播放的项目 + if (PlaybackService.mediaSession != null) { + if (mediaItem.mediaId.equals(PlaybackService.mediaSession.getPlayer().getCurrentMediaItem().mediaId)) { + color = ContextCompat.getColor(holder.getContext(), R.color.text_cz); + } + } + holder.bindingA.name.setTextColor(color); + holder.bindingA.zz.setTextColor(color); + holder.itemView.setOnClickListener(view -> { + if (PlaybackService.mediaSession != null) { + new Thread() { + @Override + public void run() { + super.run(); +// gj.sc(String.format("id=%s",mediaItem.mediaId)); +// MP3 hq = url.hq(new MP3(mediaItem.mediaId)); +// gj.sc(hq); + main.handler.post(() -> { + Player player = PlaybackService.mediaSession.getPlayer(); + int absoluteAdapterPosition = holder.getAbsoluteAdapterPosition(); + player.seekTo(absoluteAdapterPosition, 0); +// player.replaceMediaItem(absoluteAdapterPosition, PlaybackService.GetMp3(hq)); + player.prepare(); + player.play(); + notifyDataSetChanged(); + }); + } + }.start(); + } + }); + holder.bindingA.delete.setOnClickListener(v -> { + list.remove(holder.getAbsoluteAdapterPosition()); + PlaybackService.ListSave(); + PlaybackService.list.removeIf(mp3 -> mp3.id.equals(mediaItem.mediaId)); + notifyItemRemoved(holder.getAbsoluteAdapterPosition()); + }); + } + + @Override + public int getItemCount() { +// binding.textView.setText(String.valueOf(bfqkz.list.size())); + return 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..ce78e86 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/bfq_db.java @@ -0,0 +1,190 @@ +package com.muqingbfq.fragment; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media3.common.MediaItem; +import androidx.media3.common.MediaMetadata; +import androidx.media3.common.Player; +import androidx.media3.common.util.Util; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.muqingbfq.PlaybackService; +import com.muqingbfq.R; +import com.muqingbfq.activity.Music; +import com.muqingbfq.bfq_an; +import com.muqingbfq.databinding.FragmentBfqDbBinding; +import com.muqingbfq.main; +import com.muqingbfq.mq.Fragment; +import com.muqingbfq.mq.gj; + +public class bfq_db extends Fragment implements GestureDetector.OnGestureListener { + private GestureDetector gestureDetector; + + @Override + protected FragmentBfqDbBinding inflateViewBinding(LayoutInflater inflater, ViewGroup container) { + return FragmentBfqDbBinding.inflate(inflater, container, false); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public void setUI(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + // 获取当前活动的主题 + binding.txb.setOnClickListener(view -> bflb_db.start(getContext())); + gestureDetector = new GestureDetector(getContext(), this); + binding.kg.setOnClickListener(view -> { + if (PlaybackService.mediaSession != null) { + Util.handlePlayPauseButtonAction(PlaybackService.mediaSession.getPlayer()); + } + }); + binding.getRoot().setOnTouchListener((view, motionEvent) -> { + gestureDetector.onTouchEvent(motionEvent); + if (motionEvent.getAction() == MotionEvent.ACTION_UP) { + binding.linearLayout.setTranslationX(0); +// binding.getRoot().setAlpha(1.0f); + } + return false; + + }); + } + + @Override + public void onResume() { + super.onResume(); + main.handler.post(runnable); // 恢复监听或更新UI + } + + @Override + public void onPause() { + super.onPause(); + main.handler.removeCallbacks(runnable); + if (PlaybackService.mediaSession != null) { + PlaybackService.mediaSession.getPlayer().removeListener(playerListener); // 移除监听器 + } + } + + private void setUI(Player player) { + + binding.kg.setImageResource(player.isPlaying() ? R.drawable.bf : R.drawable.zt); + + MediaItem currentMediaItem = player.getCurrentMediaItem(); + if (currentMediaItem != null) { + MediaMetadata metadata = currentMediaItem.mediaMetadata; + String title = metadata.title != null ? metadata.title.toString() : "没有名字的音乐?"; + String artist = metadata.artist != null ? metadata.artist.toString() : "未知艺术家"; + binding.textview1.setText(title); + binding.textview2.setText(artist); + if (player.getMediaItemCount() > 0) { + binding.getRoot().setVisibility(View.VISIBLE); + } else { + binding.getRoot().setVisibility(View.GONE); + } + } else { + binding.getRoot().setVisibility(View.GONE); + } + } + + private final Player.Listener playerListener = new Player.Listener() { + @Override + public void onEvents(@NonNull Player player, @NonNull Player.Events events) { +// boolean shouldShowPlayButton = Util.shouldShowPlayButton(player); +// gj.sc("播放状态" + shouldShowPlayButton); + setUI(player); + } + }; + + private void setPlay() { + if (PlaybackService.mediaSession != null) { + PlaybackService.mediaSession.getPlayer().addListener(playerListener); + } + } + + Runnable runnable = new Runnable() { + @Override + public void run() { + if (PlaybackService.mediaSession != null) { + setPlay(); + setUI(PlaybackService.mediaSession.getPlayer()); + main.handler.removeCallbacks(this); + } else { + main.handler.postDelayed(this, 1000); + } + } + }; + + @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); + Music.startActivity(getContext()); + return true; + } + + @Override + public boolean onScroll(@Nullable MotionEvent motionEvent, @NonNull MotionEvent motionEvent1, + float v, float v1) { + if (PlaybackService.mediaSession == null) { + return false; + } + Player player = PlaybackService.mediaSession.getPlayer(); + if (player.getMediaItemCount() == 0) { + //如果只有一首曲子 + return false; + } + if (player.getCurrentMediaItemIndex() == 0) { + //如果没有上一曲 + if (v < 0) { + v = 0; + } + }else if (player.getCurrentMediaItemIndex() == player.getMediaItemCount() - 1) { + //如果没有下一曲 + if (v > 0) { + v = 0; + } + } + 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 (PlaybackService.mediaSession == null) { + return false; + } + Player player = PlaybackService.mediaSession.getPlayer(); + // 判断手势方向并限制滑动距离 + if (distance > threshold) { + // 向左滑动 下一曲 + player.seekToNextMediaItem(); + } else if (distance < -threshold) { + // 向右滑动 上一曲 + player.seekToPreviousMediaItem(); + } + 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..fa04cfc --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/gd.java @@ -0,0 +1,282 @@ +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.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Filter; +import android.widget.Filterable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.muqingbfq.R; +import com.muqingbfq.XM; +import com.muqingbfq.adapter.AdapterGdH; +import com.muqingbfq.api.resource; +import com.muqingbfq.databinding.ActivityGdBinding; +import com.muqingbfq.databinding.ListGdBBinding; +import com.muqingbfq.mq.FragmentActivity; +import com.muqingbfq.mq.VH; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wl; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class gd extends FragmentActivity { + + public Adapter adapter = new 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()); + } + + public static void start(Context context, String[] str) { + Intent intent = new Intent(context, gd.class); + intent.putExtra("id", str[0]); + intent.putExtra("name", str[1]); + context.startActivity(intent); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(); + Intent intent = getIntent(); + binding.title.setText(intent.getStringExtra("name")); +// k = (int) (getResources().getDisplayMetrics().widthPixels / getResources().getDisplayMetrics().density + 0.5f); +// GridLayoutManager gridLayoutManager = new GridLayoutManager(this, k / 120); + binding.lb.setLayoutManager(new LinearLayoutManager(this)); + binding.lb.setAdapter(adapter); + + 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) { + + } + }); + String id = intent.getStringExtra("id"); + + binding.fragmentDb.post(new Runnable() { + @Override + public void run() { + int height = binding.fragmentDb.getHeight(); + binding.lb.setPadding(0,0,0,height); + } + }); + new start(id); + } + + @Override + protected ActivityGdBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityGdBinding.inflate(layoutInflater); + } + + class start extends Thread { + String id; + + public start(String id) { + binding.recyclerview1Bar.setVisibility(View.VISIBLE); + this.id = id; + adapter.list.clear(); + start(); + } + + @Override + public void run() { + super.run(); + if (id.equals("排行榜")) { + resource.leaderboard(adapter.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"); + adapter.list.add(new XM(id, name, coverImgUrl)); + } + } catch (Exception e) { + gj.sc(e); + } + } + runOnUiThread(() -> { + binding.lb.setAdapter(adapter); + binding.recyclerview1Bar.setVisibility(View.GONE); + if (adapter.list.isEmpty()) { + binding.recyclerview1Text.setVisibility(View.VISIBLE); + binding.recyclerview1Text.setOnClickListener(v -> new start(id)); + } else { + binding.recyclerview1Text.setVisibility(View.GONE); + } + }); + } + } + +/* + public void setonlong(int position) { + XM xm = list.get(position); + gj.sc(xm.name); + String[] stringArray = getResources() + .getStringArray(R.array.gd_list); + if (!wj.cz(wj.gd + xm.id)) { + stringArray = new String[]{"下载歌单"}; + } + String[] finalStringArray = stringArray; + new MaterialAlertDialogBuilder(this). + 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(); + } +*/ + + @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 + 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; + } + + + public class Adapter extends AdapterGdH implements Filterable { + private List list_ys; + + public Adapter() { + list_ys = list; + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + super.onBindViewHolder(holder, position); + } + + @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++) { + XM xm = list_ys.get(i); + if (xm.name.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; + binding.lb.setAdapter(adapter); + } + }; + } + } +} \ 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..0a912c4 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/gd_adapter.java @@ -0,0 +1,130 @@ +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.AdapterGd; +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); + } + + AdapterGd adapterGd = new AdapterGd(); + @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); + adapterGd.list = list; + binding.recyclerview1.setAdapter(adapterGd); + 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); +// requireActivity().findViewById(R.id.linearLayout4).post(new Runnable() { +// @Override +// public void run() { +// binding.recyclerview2.setPadding(0, 0, 0, +// requireActivity().findViewById(R.id.linearLayout4).getHeight()); +// } +// }); + } + 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(); + } + + public void Gdlist() { + + } +} 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..bd2c15b --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/mp3.java @@ -0,0 +1,364 @@ +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.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +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 android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.media3.common.MediaItem; +import androidx.media3.common.Player; +import androidx.recyclerview.widget.LinearLayoutManager; + +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.muqingbfq.MP3; +import com.muqingbfq.PlaybackService; +import com.muqingbfq.R; +import com.muqingbfq.adapter.AdapterMp3; +import com.muqingbfq.api.playlist; +import com.muqingbfq.api.url; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.ActivityMp3Binding; +import com.muqingbfq.databinding.ListMp3ImageBinding; +import com.muqingbfq.main; +import com.muqingbfq.mq.FragmentActivity; +import com.muqingbfq.mq.VH; +import com.muqingbfq.mq.gj; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jp.wasabeef.glide.transformations.BlurTransformation; + +public class mp3 extends FragmentActivity { + private List list = new ArrayList<>(); + private List list_ys = new ArrayList<>(); + public 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()); + } + + public static void start(Context context, String[] strings) { + Intent intent = new Intent(context, mp3.class); + intent.putExtra("id", strings[0]); + intent.putExtra("name", strings[1]); + context.startActivity(intent); + } + + public static Drawable drawable = null; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(); +// drawable=null; + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(android.R.attr.windowBackground, typedValue, true); + ; // 获取当前主题的背景颜色 + if (drawable != null) { + +// 4. 设置到 ImageView 上 + ImageView imageView = findViewById(R.id.toolbarimage); + Glide.with(this) + .load(drawable) + .apply(RequestOptions.bitmapTransform(new BlurTransformation(26,3))) + .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 Drawable resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { + + // 使用 Glide 加载图片并应用高斯模糊效果 +// 1. 创建渐变遮罩层(从底部白色渐变到顶部透明) + GradientDrawable gradient = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, + new int[] {typedValue.data, Color.TRANSPARENT}); + gradient.setShape(GradientDrawable.RECTANGLE); + +// 3. 使用 LayerDrawable 来组合模糊图像和渐变效果 + LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{resource, gradient}); + imageView.setImageDrawable(layerDrawable); + return true; + } + }) + .into(imageView); + } + ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, 0, systemBars.right, systemBars.bottom); +// binding.toolbar.setPadding(0, systemBars.top, 0, 0); + FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) binding.toolbar.getLayoutParams(); + params.setMargins(0, systemBars.top, 0, 0); // 参数分别是 left, top, right, bottom + binding.toolbar.setLayoutParams(params); + + return insets; + }); + 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) { + + } + }); + + getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + + @Override + public void handleOnBackPressed() { + if (binding.edittext.getVisibility() == View.VISIBLE) { + binding.title.setVisibility(View.VISIBLE); + binding.edittext.setVisibility(View.GONE); + gj.ycjp(binding.edittext); + adapter.getFilter().filter(""); + } else { + finish(); +// ActivityCompat.finishAfterTransition(mp3.this); + } + } + }); + binding.fragmentDb.post(() -> { + int height = binding.fragmentDb.getHeight(); + binding.lb.setPadding(0, 0, 0, height); + }); + binding.playButton.setOnClickListener(v -> { + v.setEnabled(false); + if (PlaybackService.mediaSession == null) { + return; + } + Player player = PlaybackService.mediaSession.getPlayer(); + player.clearMediaItems(); + List aalist = new ArrayList<>(list); + if (bfqkz.ms == 2) { + Collections.shuffle(aalist); + } + for (int i = 0; i < aalist.size(); i++) { + MP3 mp3 = aalist.get(i); +// mp3 = url.hq(mp3); + MediaItem mediaItem = PlaybackService.GetMp3(mp3); + player.addMediaItem(mediaItem); + } + player.prepare(); + player.seekTo(0, 0); + player.play(); + //保存播放列表 + PlaybackService.list.clear(); + PlaybackService.list.addAll(aalist); + PlaybackService.ListSave(); + new Thread(new Runnable() { + @Override + public void run() { + while (true){ + try { + Thread.sleep(500); + //检测音乐是否播放 + runOnUiThread(() -> { + if (!player.isPlaying()) { + return; + } + v.setEnabled(true); + }); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + }).start(); + }); + } + + @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; + } + + @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 class Adapter extends AdapterMp3 implements Filterable { + + private List list_ys; + + public Adapter(List list) { + this.list = list; + list_ys = list; + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + super.onBindViewHolder(holder, position); + } + + @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..fc655c2 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/search.java @@ -0,0 +1,231 @@ +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.adapter.AdapterGdH; +import com.muqingbfq.adapter.AdapterMp3; +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; + List fragments=new ArrayList<>(); + @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 AdapterMp3(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 AdapterGdH(list)); + 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(); + } + } + 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..bacbdfe --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/sz.java @@ -0,0 +1,69 @@ +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.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://github.com/muqing153/CloudMusic"); + } 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..9957dca --- /dev/null +++ b/app/src/main/java/com/muqingbfq/fragment/wode.java @@ -0,0 +1,273 @@ +package com.muqingbfq.fragment; + +import android.app.Activity; +import android.content.Intent; +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.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +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.databinding.FragmentWdBinding; +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.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, "开发中", ""} + }; + + @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}); + 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().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) { +// gj.sc("dl"); + new threadLogin().start(); + } + // ... + } + }); + + 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) { + wj.sc(wj.filesdri + "user.mq"); + 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();//游客模式 + wj.sc(wj.filesdri + "user.mq"); +// 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); + } + } + + //登陆 获取用户信息 头像 昵称 签名 + class threadLogin extends Thread { + + public void run() { + String hq = wl.hq("/user/account?cookie=" + wl.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); + }); + } + } + } +} 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..c98fc52 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/home.java @@ -0,0 +1,348 @@ +package com.muqingbfq; + +import android.annotation.SuppressLint; +import android.content.ComponentName; +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.widget.FrameLayout; + +import androidx.activity.EdgeToEdge; +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.core.graphics.Insets; +import androidx.core.view.GravityCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.fragment.app.Fragment; +import androidx.media3.session.MediaController; +import androidx.media3.session.SessionToken; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; + +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.card.MaterialCardView; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.search.SearchView; +import com.google.common.base.Strings; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import com.muqingbfq.databinding.ActivityHomeBinding; +import com.muqingbfq.fragment.gd_adapter; +import com.muqingbfq.fragment.search; +import com.muqingbfq.fragment.sz; +import com.muqingbfq.fragment.wode; +import com.muqingbfq.mq.AppCompatActivity; +import com.muqingbfq.mq.gj; +import com.muqingbfq.mq.wl; +import com.muqingbfq.view.Edit; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class home extends AppCompatActivity { + + @Override + protected ActivityHomeBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityHomeBinding.inflate(layoutInflater); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(R.style.Theme_muqing); + EdgeToEdge.enable(this); + super.onCreate(savedInstanceState); + SessionToken sessionToken = + new SessionToken(this, new ComponentName(this, PlaybackService.class)); + ListenableFuture controllerFuture = + new MediaController.Builder(this, sessionToken).buildAsync(); + controllerFuture.addListener(() -> { + + }, MoreExecutors.directExecutor()); + if (Strings.isNullOrEmpty(wl.Cookie)) { + new HomeSteer(this) { + @Override + public void Yes() { + UI(); + } + }; + } else UI(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + + public void toolbar() { + setSupportActionBar(binding.toolbar); + } + + public void UI() { + setContentView(); +// 禁止ViewPager2滑动 + binding.viewPager.setUserInputEnabled(false); + ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); +// v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + v.setPadding(systemBars.left, 0, systemBars.right, 0); + binding.chb.setPadding(0, systemBars.top, 0, 0); + return insets; + }); +// viewTop=binding. + getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + if (binding.searchview.isShowing()) { + binding.searchview.hide(); + return; + } + moveTaskToBack(true); + } + }); + + //初始化侧滑 + binding.chb.setNavigationItemSelectedListener(item -> { + sz.switch_sz(home.this, item.getItemId()); + return false; + }); + 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.setCurrentItem(0,false); + if (!gj.isTablet(this)) { + binding.linearLayout4.post(() -> { + int height = binding.linearLayout4.getHeight(); + binding.viewPager.setPadding(0, 0, 0, height); + }); + } + binding.fragmentDb.post(() -> { + int height = binding.fragmentDb.getHeight(); + binding.searchview.setPadding(0, 0, 0, height); + }); + binding.tablayout.addView("推荐", R.drawable.zhuye).setOnClickListener(v -> { + for (int i = 0; i < binding.tablayout.sizeView; i++) { + MaterialCardView childAt = (MaterialCardView) binding.tablayout.linearLayout.getChildAt(i); + if (i == 0) { + //背景高亮 + childAt.setCardBackgroundColor(gj.getThemeColor(v.getContext(), com.google.android.material.R.attr.colorPrimaryContainer)); + } else { + //背景恢复 + childAt.setCardBackgroundColor(gj.getThemeColor(v.getContext(), com.google.android.material.R.attr.colorSurface)); + } + } + binding.viewPager.setCurrentItem(0,false); + }); + binding.tablayout.addView("我的", R.drawable.user).setOnClickListener(v -> { + for (int i = 0; i < binding.tablayout.sizeView; i++) { + MaterialCardView childAt = (MaterialCardView) binding.tablayout.linearLayout.getChildAt(i); + if (i == 1) { + //背景高亮 + childAt.setCardBackgroundColor(gj.getThemeColor(v.getContext(), com.google.android.material.R.attr.colorPrimaryContainer)); + } else { + //背景恢复 + childAt.setCardBackgroundColor(gj.getThemeColor(v.getContext(), com.google.android.material.R.attr.colorSurface)); + } + } + binding.viewPager.setCurrentItem(1,false); + }); + toolbar(); + SearchUI(); + + } + + @Override + protected void onPause() { + super.onPause(); + //在销毁 Activity 之前,系统会先调用 onDestroy()。系统调用此回调的原因如下: + // 保存列表数据 + + } + + public boolean issearchclicklist = false;//是否点击了列表项目 + //搜索建议列表 + private List searchList = new ArrayList<>(); + + private activity_search.SearchRecordAdapter searchRecordAdapter; + + public void SearchUI() { + binding.searchview + .getEditText() + .setOnEditorActionListener( + (v, actionId, event) -> { +// binding.searchview.hide(); + searchStart(binding.toolbar.getText().toString()); + return false; + }); + binding.searchview.setOnMenuItemClickListener( + menuItem -> { + // Handle menuItem click. + return true; + }); + FlexboxLayoutManager manager = new FlexboxLayoutManager(this); + //设置主轴排列方式 + manager.setFlexDirection(FlexDirection.ROW); + //设置是否换行 + manager.setFlexWrap(FlexWrap.WRAP); + manager.setAlignItems(AlignItems.STRETCH);//历史记录的LayoutManager + binding.listRecycler.setNestedScrollingEnabled(false); + binding.listRecycler.setLayoutManager(manager); + binding.searchview.addTransitionListener( + (searchView, previousState, newState) -> { + if (newState == SearchView.TransitionState.SHOWING) { + // Handle search view opened. + gj.sc("SHOWING"); + binding.tablayout.setVisibility(View.GONE); + // 添加 Fragment + getSupportFragmentManager() + .beginTransaction() + .replace(binding.searchFragment.getId(), new search()) + .commit(); + searchRecordAdapter = new activity_search.SearchRecordAdapter(binding.searchview); + binding.listRecycler.setAdapter(searchRecordAdapter); + } else if (newState == SearchView.TransitionState.SHOWN) { + gj.sc("SHOWN"); + } else if (newState == SearchView.TransitionState.HIDING) { + if (!gj.isTablet(this)) { + binding.tablayout.setVisibility(View.VISIBLE); + } + searchRecordAdapter = null; + binding.listRecycler.setAdapter(null); + binding.searchFragment.setVisibility(View.GONE); + binding.searchRecycler.setVisibility(View.GONE); + binding.xxbj1.setVisibility(View.VISIBLE); + // 移除当前显示的 Fragment + getSupportFragmentManager().beginTransaction() + .remove(getSupportFragmentManager() + .findFragmentById(binding.searchFragment.getId())) + .commit(); + } + }); + final Object o = new Object(); + binding.searchview.getEditText().addTextChangedListener(new Edit.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) { + new Thread() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void run() { + synchronized (o) { + searchList.clear(); + String hq = com.muqingbfq.mq.wl. + hq("/search/suggest?keywords=" + var1.toString() + "&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"); + searchList.add(keyword); + } + runOnUiThread(() -> binding.searchRecycler.setAdapter(new activity_search.search_adapter(searchList, string -> { + gj.sc(string); + issearchclicklist = true; + binding.searchRecycler.setAdapter(null); + binding.searchRecycler.setVisibility(View.GONE); + binding.xxbj1.setVisibility(View.GONE); + binding.searchFragment.setVisibility(View.VISIBLE); + binding.searchview.setText(string); + searchStart(string); +// binding.searchRecycler.set + }))); + } catch (Exception e) { + gj.sc(e); + } + } + } + }.start(); + } + + @Override + public void afterTextChanged(Editable var1) { + if (Strings.isNullOrEmpty(var1.toString())) { + binding.searchRecycler.setAdapter(null); + binding.searchRecycler.setVisibility(View.GONE); + binding.xxbj1.setVisibility(View.VISIBLE); + binding.searchFragment.setVisibility(View.GONE); + } else if (!issearchclicklist) { + binding.searchRecycler.setVisibility(View.VISIBLE); + binding.xxbj1.setVisibility(View.GONE); + binding.searchFragment.setVisibility(View.GONE); + } else issearchclicklist = false; + + } + }); + binding.toolbar.setNavigationIcon(R.drawable.menu); + binding.deleat.setOnClickListener(v -> new MaterialAlertDialogBuilder( + v.getContext()) + .setTitle("删除") + .setMessage("清空历史记录?") + .setNegativeButton("取消", null) + .setPositiveButton("确定", (dialogInterface, ii) -> { + searchRecordAdapter.json_list.clear(); + binding.listRecycler.setAdapter(searchRecordAdapter); + com.muqingbfq.mq.wj.sc(com.muqingbfq.mq.wj.filesdri + + com.muqingbfq.mq.wj.lishi_json); + }) + .show()); + + } + + public void searchStart(String name) { + issearchclicklist = true; + binding.toolbar.setText(binding.searchview.getText()); + if (!TextUtils.isEmpty(name)) { + search sea = (search) getSupportFragmentManager().findFragmentById(binding.searchFragment.getId()); + binding.searchFragment.setVisibility(View.VISIBLE); + binding.searchRecycler.setVisibility(View.GONE); + sea.sx(name); + activity_search.addSearchRecord(name, searchRecordAdapter.json_list, searchRecordAdapter); + } + } + + @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) { + binding.searchview.show(); +// startActivity(new Intent(this, activity_search.class)); + } else if (item.getItemId() == android.R.id.home) { + //展开侧滑 + binding.chct.openDrawer(GravityCompat.START); + } + 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..26cbbc3 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/login/user_logs.java @@ -0,0 +1,250 @@ +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 android.widget.Toast; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.core.view.WindowCompat; + +import com.google.gson.Gson; +import com.muqingbfq.databinding.ActivityUserLogsBinding; +import com.muqingbfq.main; +import com.muqingbfq.mq.AppCompatActivity; +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.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(view1 -> { + if (!TextUtils.isEmpty(binding.editUser.getText())) { + wl.setcookie(binding.editUser.getText().toString()); + } + new Thread(() -> { + gj.sc(wl.Cookie); + String hq = wl.hq("/login/status?cookie=" + wl.Cookie); + try { + JSONObject jsonObject = new JSONObject(hq); + JSONObject data = jsonObject.getJSONObject("data"); + JSONObject account1 = data.getJSONObject("account"); + finish(true); + + } catch (JSONException e) { + gj.sc(e); + runOnUiThread(() -> { + gj.ycjp(binding.editUser); + Toast.makeText(user_logs.this, "不成功的Cookie登陆", Toast.LENGTH_SHORT).show(); + }); + } + }).start(); + }); + 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); + } + }); + getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + finish(false); + } + }); + } + //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(); + } + + + 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"); + + String s = new Gson().toJson(new user_logs.USER(nickname, signature, avatarUrl)); + wj.xrwb(wj.filesdri + "user.mq", s); + 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/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..a5c7904 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/main.java @@ -0,0 +1,117 @@ +package com.muqingbfq; + +import android.annotation.SuppressLint; +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.text.TextUtils; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ProcessLifecycleOwner; + +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; + @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(); + // 注册 ProcessLifecycleOwner 以监听应用生命周期事件 + ProcessLifecycleOwner.get().getLifecycle().addObserver(new DefaultLifecycleObserver() { + @Override + public void onStart(@Nullable LifecycleOwner owner) { + // 应用进入前台 + gj.sc("onStart"); + if (FloatingLyricsService.lei != null) { + stopService(new Intent(main.this, FloatingLyricsService.class)); + } + } + + @Override + public void onStop(@Nullable LifecycleOwner owner) { + // 应用进入后台 + gj.sc("onStop"); + if (Settings.canDrawOverlays(main.this) && FloatingLyricsService.lei == null ) { + startService(new Intent(main.this, FloatingLyricsService.class)); + } + } + }); + } +} 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..1644173 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/AppCompatActivity.java @@ -0,0 +1,65 @@ +package com.muqingbfq.mq; + +import android.content.res.Configuration; +import android.graphics.Color; +import android.os.Build; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; + +import androidx.activity.EdgeToEdge; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.viewbinding.ViewBinding; + +import com.muqingbfq.R; + +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() { + EdgeToEdge.enable(this); + super.setContentView(getViewBinding().getRoot()); +// Window window = getWindow(); +//// 请求进行全屏布局+更改状态栏字体颜色 +// // 获取程序是不是夜间模式 +// int uiMode = getApplicationContext().getResources().getConfiguration().uiMode; +// if ((uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) { +//// SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION and SYSTEM_UI_FLAG_LAYOUT_STABLE请求进行全屏布局 +//// SYSTEM_UI_FLAG_VISIBLE进行更改状态栏字体颜色 +// window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE);//白色 +// } else { +// window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);//黑色 +// +// } +//// 让内容显示在系统栏的后面,也就是显示在状态栏和导航栏的后面 +// WindowCompat.setDecorFitsSystemWindows(window, true); +//// 沉浸状态栏(给任务栏上透明的色)(Android 10 上,只需要将系统栏颜色设为完全透明即可:) +// window.setStatusBarColor(Color.TRANSPARENT); +// // 沉浸导航栏(设置透明色) +// window.setNavigationBarColor(Color.TRANSPARENT); +// +//// 在安卓10以上禁用系统栏视觉保护。 +//// 当设置了 导航栏 栏背景为透明时,NavigationBarContrastEnforced 如果为true,则系统会自动绘制一个半透明背景 +//// 状态栏的StatusBarContrast 效果同理,但是值默认为false,因此不用设置 +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { +// window.setNavigationBarContrastEnforced(false); +// } + ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); +// v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + v.setPadding(systemBars.left, systemBars.top, systemBars.right,0); + return insets; + }); + } +} 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..82039ac --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/EditViewDialog.java @@ -0,0 +1,176 @@ +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()); + } + + /** + * 取消按钮 + * @param a + */ + public void setNegative(View.OnClickListener a) { + buttona.setOnClickListener(a); + } + + /** + * 确定按钮 + * @param a + * @return + */ + 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..e30fd86 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/FloatingLyricsService.java @@ -0,0 +1,242 @@ +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 androidx.annotation.Nullable; + +import com.dirror.lyricviewx.LyricEntry; +import com.dirror.lyricviewx.LyricViewX; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.muqingbfq.PlaybackService; +import com.muqingbfq.bfqkz; +import com.muqingbfq.databinding.FloatLrcviewBinding; + +import java.io.File; +import java.lang.reflect.Type; + +public class FloatingLyricsService extends Service { + private WindowManager windowManager; + FloatLrcviewBinding binding; + + public Runnable updateSeekBar = new Runnable() { + @Override + public void run() { + + if (PlaybackService.mediaSession == null || LyricViewX.lyricEntryList.isEmpty()) { + handler.postDelayed(this, 1000); + return; + } + int index = 0; + for (int i = 0; i < LyricViewX.lyricEntryList.size(); i++) { + LyricEntry lineLrc = LyricViewX.lyricEntryList.get(i); + if (lineLrc.time <= PlaybackService.mediaSession.getPlayer().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; + } + + private int initialY; + private float initialTouchY; + @Override + public void onCreate() { + super.onCreate(); + lei = this; + try { + 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 = gson.fromJson(dqwb, type); + } else { + setup = new SETUP(); + } + if (setup.i == 0) { + //在Service中关闭自己 + stopSelf(); + return; + } + // 创建悬浮窗歌词的 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.setColorFilter(Color.BLACK); + binding.lock.setOnClickListener(v1 -> setyc()); +// params.gravity = Gravity.CENTER; + // 获取 WindowManager 并将悬浮窗歌词添加到窗口中 + windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + windowManager.addView(binding.getRoot(), params); + if (setup.i == 2) { + setyc(); + } else { + show(); + } + handler.post(updateSeekBar); // 在播放开始时启动更新进度 + + } catch (Exception e) { + wj.sc(wj.filesdri + "FloatingLyricsService.json"); + gj.sc(getClass() + ":" + e); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); +// Service.stopSelf(); + // 在 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)); + } + + @SuppressLint("ClickableViewAccessibility") + public void setyc() { + setup.i = 2; + params.flags = lock(); + binding.lock.setVisibility(View.GONE); + binding.getRoot().setCardBackgroundColor(Color.parseColor("#00FFFFFF")); + binding.getRoot().setOnTouchListener(null); + params.type = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? + WindowManager.LayoutParams.TYPE_SYSTEM_ALERT : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +//将alpha设置为最大遮挡不透明度 + params.alpha = 0.8f; + windowManager.updateViewLayout(binding.getRoot(), params); + baocun(); + } + + @SuppressLint({"CheckResult", "ClickableViewAccessibility"}) + public void show() { + setup.i = 1; + params.flags = lock(); + binding.lock.setVisibility(View.VISIBLE); + binding.getRoot().setCardBackgroundColor(Color.parseColor("#FFFFFF")); + binding.getRoot().setOnTouchListener((v12, event) -> { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + // 获取初始Y位置和初始触摸Y坐标 + initialY = params.y; + initialTouchY = event.getRawY(); + return true; + + case MotionEvent.ACTION_MOVE: + // 手指在Y轴移动时,计算新的Y位置 + params.y = initialY + (int) (event.getRawY() - initialTouchY); + // 更新悬浮窗的Y轴位置 + setup.Y = params.y; + windowManager.updateViewLayout(binding.getRoot(), params); + return true; + } + return true; + }); + 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..64286e1 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/FragmentActivity.java @@ -0,0 +1,60 @@ +package com.muqingbfq.mq; + +import android.view.MenuItem; +import android.view.View; + +import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.viewbinding.ViewBinding; + +import com.muqingbfq.R; + +public abstract class FragmentActivity + extends AppCompatActivity { + @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() { + EdgeToEdge.enable(this); + super.setContentView(getViewBinding().getRoot()); + ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, 0, systemBars.right, systemBars.bottom); + return insets; + }); + setToolbar(); + + } + + public void onBack() { + + // getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { +// @Override +// public void handleOnBackPressed() { +// if (binding.searchview.isShowing()) { +// binding.searchview.hide(); +// return; +// } +// moveTaskToBack(true); +// } +// }); + } +} diff --git a/app/src/main/java/com/muqingbfq/mq/MusicViewModel.java b/app/src/main/java/com/muqingbfq/mq/MusicViewModel.java new file mode 100644 index 0000000..8451ab9 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/MusicViewModel.java @@ -0,0 +1,13 @@ +package com.muqingbfq.mq; + + +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +public class MusicViewModel extends ViewModel { + private MutableLiveData imageUrl = new MutableLiveData<>(); + + public MutableLiveData getImageUrl() { + return imageUrl; + } +} \ No newline at end of file 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/gj.java b/app/src/main/java/com/muqingbfq/mq/gj.java new file mode 100644 index 0000000..82eff23 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/gj.java @@ -0,0 +1,170 @@ +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; + } + + /** + * 判断是否是平板 + * @param context + * @return + */ + 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()); + } + + 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 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; + } + + /** + * dp转px + * @param context + * @param dpValue + */ + public static int dp2px(Context context, float dpValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } +} \ 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..668b500 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/mq/wl.java @@ -0,0 +1,118 @@ +package com.muqingbfq.mq; + + +import android.content.Context; + +import androidx.annotation.OptIn; +import androidx.media3.common.MediaItem; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.database.DatabaseProvider; +import androidx.media3.database.StandaloneDatabaseProvider; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.cache.Cache; +import androidx.media3.datasource.cache.CacheDataSource; +import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor; +import androidx.media3.datasource.cache.SimpleCache; + +import com.muqingbfq.main; + +import java.io.File; + +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 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; + } + + + @OptIn(markerClass = UnstableApi.class) + public static DataSource.Factory DownMp3() { + Context context = main.application; + + DatabaseProvider databaseProvider = new StandaloneDatabaseProvider(context); + // 创建一个 File 对象来指定缓存目录 + File downloadDirectory = new File(wj.mp3); + // 如果缓存目录不存在,则创建它 + if (!downloadDirectory.exists()) { + downloadDirectory.mkdirs(); + } + Cache cache = + new SimpleCache( + downloadDirectory, new LeastRecentlyUsedCacheEvictor(100 * 1024 * 1024), databaseProvider); + + CacheDataSource.Factory httpDataSourceFactory = new CacheDataSource.Factory(); + return new CacheDataSource.Factory() + .setCache(cache) + .setUpstreamDataSourceFactory(httpDataSourceFactory); + } + +} 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..cfdb73a --- /dev/null +++ b/app/src/main/java/com/muqingbfq/sz.java @@ -0,0 +1,274 @@ +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.os.Handler; +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.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.fragment.app.Fragment; +import androidx.media3.common.Player; + +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(); + ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); +// v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + 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; + ActivityResultLauncher LyricsService = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + if (Settings.canDrawOverlays(getContext())) { +// getContext().startService(new Intent(getContext(), FloatingLyricsService.class)); + binding.switchA3.setChecked(true); + binding.slide1.setEnabled(true); + } else { + binding.switchA3.setChecked(false); + binding.slide1.setEnabled(false); + } + UI(); + }); + FloatingLyricsService.SETUP setup; + + Handler handler = new Handler(); + + private Runnable ThreadLrc = new Runnable() { + @Override + public void run() { + if (PlaybackService.mediaSession == null) { + handler.postDelayed(this, 1000); + return; + } + Player player = PlaybackService.mediaSession.getPlayer(); + int index = 0; + for (int i = 0; i < LyricViewX.lyricEntryList.size(); i++) { + LyricEntry lineLrc = LyricViewX.lyricEntryList.get(i); + gj.sc(player.getCurrentPosition()); + if (lineLrc.time <= player.getCurrentPosition()) { + index = i; + } else { + break; + } + } + if (index < LyricViewX.lyricEntryList.size()) { + LyricEntry currentLrc = LyricViewX.lyricEntryList.get(index); + if (currentLrc.secondText != null) { + binding.lrcViewMessage.setText(currentLrc.secondText); + } else { + binding.lrcViewMessage.setText(""); + } + binding.lrcView.setText(currentLrc.text); + } + handler.postDelayed(this, 1000); + } + }; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = ActivitySzSetlrcBinding.inflate(inflater, container, false); + UI(); + return binding.getRoot(); + } + + private void UI() { + 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); + } + binding.lock.setVisibility(View.VISIBLE); + binding.lock.setImageResource(setup.i == 2 ? R.drawable.lock : R.drawable.lock_open); + handler.post(ThreadLrc); + } else { + binding.lock.setVisibility(View.GONE); + } + 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.switchA3.setOnLongClickListener(v -> { + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getContext().getPackageName())); + LyricsService.launch(intent); + return false; + }); + binding.slide1.setLabelFormatter(value -> String.valueOf((int) value)); + binding.slide1.addOnChangeListener(this); + binding.slide1.addOnChangeListener(this); + binding.slide1.addOnSliderTouchListener(this); + binding.lock.setOnClickListener(view -> { + if (setup.i == 1) { + setup.i = 2; + binding.lock.setImageResource(R.drawable.lock); + } else if (setup.i == 2) { + setup.i = 1; + binding.lock.setImageResource(R.drawable.lock_open); + } + FloatingLyricsService.baocun(setup); + }); + } + + @Override + public void onDestroy() { + super.onDestroy(); + handler.removeCallbacks(ThreadLrc); + } + + @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/StrokeTextView.java b/app/src/main/java/com/muqingbfq/view/StrokeTextView.java new file mode 100644 index 0000000..e66dba0 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/view/StrokeTextView.java @@ -0,0 +1,47 @@ +package com.muqingbfq.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.text.TextPaint; +import android.util.AttributeSet; + +import androidx.appcompat.widget.AppCompatTextView; + +public class StrokeTextView extends AppCompatTextView { + + public StrokeTextView(Context context) { + super(context); + } + + public StrokeTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public StrokeTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onDraw(Canvas canvas) { + // 获取当前TextView的Paint对象 + TextPaint textPaint = getPaint(); + + // 保存原来的文本颜色 + int currentTextColor = getCurrentTextColor(); + + // 设置描边效果 + textPaint.setStyle(Paint.Style.STROKE); // 设置为描边 + textPaint.setStrokeWidth(1); // 设置描边的宽度 + setTextColor(Color.BLACK); // 描边的颜色 + + // 绘制描边 + super.onDraw(canvas); + + // 恢复原来的文本颜色并绘制文本内容 + textPaint.setStyle(Paint.Style.FILL); // 恢复为填充 + setTextColor(currentTextColor); // 恢复原来的颜色 + super.onDraw(canvas); + } +} diff --git a/app/src/main/java/com/muqingbfq/view/TabLayout.java b/app/src/main/java/com/muqingbfq/view/TabLayout.java new file mode 100644 index 0000000..7d7dd93 --- /dev/null +++ b/app/src/main/java/com/muqingbfq/view/TabLayout.java @@ -0,0 +1,115 @@ +package com.muqingbfq.view; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.media.Image; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.view.ContextThemeWrapper; +import androidx.appcompat.widget.LinearLayoutCompat; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; + +import com.google.android.material.card.MaterialCardView; +import com.muqingbfq.R; +import com.muqingbfq.mq.gj; + +public class TabLayout extends MaterialCardView { + public TabLayout(Context context) { + this(context, null); + } + + public TabLayout(Context context, @Nullable AttributeSet attrs) { + this(context, attrs,0); + } + + public TabLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs,defStyleAttr); + Init(context, attrs, defStyleAttr); + } + + public LinearLayout linearLayout; + private void Init(Context context, AttributeSet attrs, int defStyleAttr) { + linearLayout = new LinearLayout(context, attrs, defStyleAttr); + int a = gj.dp2px(context, 9); + LinearLayout.LayoutParams layoutParams ; + if (gj.isTablet(context)) { +// 这是平板模式 + layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.MATCH_PARENT); + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setGravity(Gravity.CENTER); + setContentPadding(a, 0, a, 0); + } else { +// 这是手机模式 + layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + linearLayout.setGravity(Gravity.CENTER); + setContentPadding(0, a, 0, a); + } +// linearLayout.setBackgroundColor(Color.BLACK); + addView(linearLayout,layoutParams); +// addView(new Button(context)); + } + + public int sizeView = 0; + + @SuppressLint("ResourceType") + public View addView(String title, int icon) { + Context context = getContext(); + Item itemB = new Item(context); + + itemB.imageView.setImageResource(icon); + itemB.textView.setText(title); + LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + int a = gj.dp2px(context, 26); + if (!gj.isTablet(context)) { + layoutParams.setMargins(a, 0, a, 0); + } else { + layoutParams.setMargins(0, a, 0, a); + } + linearLayout.addView(itemB, layoutParams); + if (sizeView == 0) { + itemB.setCardBackgroundColor(gj.getThemeColor(context, com.google.android.material.R.attr.colorPrimaryContainer)); + } + sizeView++; + + return itemB; + } + + public static class Item extends MaterialCardView { + ImageView imageView; + TextView textView; + public Item(Context context) { + this(context, null); + } + + public Item(Context context, @Nullable AttributeSet attrs) { + super(new ContextThemeWrapper(context, com.google.android.material.R.style.Widget_Material3_CardView_Elevated), attrs); + setUseCompatPadding(true); + setFocusable(true); + setClickable(true); +// 没有边缘线 + setStrokeWidth(0); + int padding = gj.dp2px(context, 6); + setContentPadding(padding,padding,padding,padding); + inflate(context, R.layout.view_tab, this); + imageView = findViewById(R.id.image); + textView = findViewById(R.id.text); + } + + } +} 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..8999eab --- /dev/null +++ b/app/src/main/java/com/muqingbfq/yc.java @@ -0,0 +1,72 @@ +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 android.view.LayoutInflater; + +import androidx.annotation.Nullable; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.muqingbfq.databinding.ActivityYcBinding; +import com.muqingbfq.mq.AppCompatActivity; +import com.muqingbfq.mq.gj; + +public class yc extends AppCompatActivity { + public Object exception; + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(); + 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(); + } + + @Override + protected ActivityYcBinding getViewBindingObject(LayoutInflater layoutInflater) { + return ActivityYcBinding.inflate(layoutInflater); + } +} 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..67d3942 --- /dev/null +++ b/app/src/main/res/drawable/delete.xml @@ -0,0 +1,11 @@ + + + \ 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..002e926 --- /dev/null +++ b/app/src/main/res/drawable/lock.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/lock_open.xml b/app/src/main/res/drawable/lock_open.xml new file mode 100644 index 0000000..ec6f369 --- /dev/null +++ b/app/src/main/res/drawable/lock_open.xml @@ -0,0 +1,12 @@ + + + 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/menu.xml b/app/src/main/res/drawable/menu.xml new file mode 100644 index 0000000..b60b416 --- /dev/null +++ b/app/src/main/res/drawable/menu.xml @@ -0,0 +1,10 @@ + + + \ 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..f8443b3 --- /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..b7b67fc --- /dev/null +++ b/app/src/main/res/drawable/play.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/progress.xml b/app/src/main/res/drawable/progress.xml new file mode 100644 index 0000000..5f1c770 --- /dev/null +++ b/app/src/main/res/drawable/progress.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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_home.xml b/app/src/main/res/layout-land/activity_home.xml new file mode 100644 index 0000000..ef295b4 --- /dev/null +++ b/app/src/main/res/layout-land/activity_home.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_music.xml b/app/src/main/res/layout-land/activity_music.xml new file mode 100644 index 0000000..86a8992 --- /dev/null +++ b/app/src/main/res/layout-land/activity_music.xml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_aa.xml b/app/src/main/res/layout/activity_aa.xml new file mode 100644 index 0000000..88a4b29 --- /dev/null +++ b/app/src/main/res/layout/activity_aa.xml @@ -0,0 +1,10 @@ + + + + \ 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..ae86b0a --- /dev/null +++ b/app/src/main/res/layout/activity_about_software.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +