feat: 新增ProtectModeTextView自定义控件

1. 继承LinearLayout,内置TextView与0~12刻度SeekBar
2. 刻度0保持原始文本不打乱,1~12为每组相邻字符个数
3. 按刻度固定长度从头至尾字符分组,分组列表随机打乱后拼接输出
4. 支持含空格/标点完整字符解析,对外提供setContentText设置文本接口
This commit is contained in:
2026-05-08 19:35:39 +08:00
parent 3231cd557a
commit 3b60a3b713
5 changed files with 211 additions and 35 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed Feb 11 05:29:19 HKT 2026
#Fri May 08 11:33:06 GMT 2026
stageCount=8
libraryProject=
baseVersion=15.12
publishVersion=15.12.7
buildCount=0
buildCount=9
baseBetaVersion=15.12.8

View File

@@ -11,12 +11,15 @@ import android.view.View;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.mymessagemanager.R;
import cc.winboll.studio.mymessagemanager.views.ProtectModeTextView;
public class UnitTestActivity extends Activity {
public static final String TAG = "UnitTestActivity";
LogView mLogView;
// 新增自定义控件
ProtectModeTextView mProtectModeTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -25,6 +28,12 @@ public class UnitTestActivity extends Activity {
mLogView = findViewById(R.id.logview);
mLogView.start();
// 初始化ProtectModeTextView
mProtectModeTv = findViewById(R.id.protect_mode_tv);
// 设置测试文本,可自行修改
String testText = "abcdefghijklmnopqrstuvwxyz消息管理 隐私保护 文本随机组合 滑动刻度测试1234567890";
mProtectModeTv.setContentText(testText);
}
public void onMain(View view) {
@@ -34,3 +43,4 @@ public class UnitTestActivity extends Activity {
AddressUtils_Test.main(this);
}
}

View File

@@ -0,0 +1,136 @@
package cc.winboll.studio.mymessagemanager.views;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import cc.winboll.studio.mymessagemanager.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
/**
* 保护模式自定义控件
* 最终规则:
* 1. 刻度范围 0~12
* 2. 刻度值 = 每一组截取【相邻字符个数】
* 3. 从头到尾按固定长度切块分组
* 4. 所有分组收集后随机打乱再拼接输出
* 5. 刻度0 = 不打乱,显示原文
* 6. 按原生字符计算(包含空格、标点)
*/
public class ProtectModeTextView extends LinearLayout {
private TextView tvContent;
private SeekBar seekBarScale;
private String originText;
private List<Character> charAllList;
private final Random random = new Random();
public ProtectModeTextView(Context context) {
super(context);
initView(context);
}
public ProtectModeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public ProtectModeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
LayoutInflater.from(context).inflate(R.layout.layout_protect_mode_textview, this, true);
tvContent = findViewById(R.id.tv_content);
seekBarScale = findViewById(R.id.seek_bar_scale);
// 刻度 0 ~ 12
seekBarScale.setMax(12);
seekBarScale.setProgress(0);
charAllList = new ArrayList<>();
seekBarScale.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
handleTextLogic(progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
}
public void setContentText(String text) {
this.originText = text;
convertToCharList(text);
handleTextLogic(seekBarScale.getProgress());
}
private void convertToCharList(String text) {
charAllList.clear();
if (text == null || text.isEmpty()) {
return;
}
char[] chars = text.toCharArray();
for (char c : chars) {
charAllList.add(c);
}
}
private void handleTextLogic(int groupSize) {
if (charAllList.isEmpty()) {
tvContent.setText(originText);
return;
}
// 刻度0 原样不打乱
if (groupSize <= 0) {
tvContent.setText(originText);
return;
}
List<String> groupList = new ArrayList<>();
int totalLen = charAllList.size();
// 从头到尾 按 groupSize 个相邻字符切块
int index = 0;
while (index < totalLen) {
StringBuilder sb = new StringBuilder();
// 每一组取 groupSize 个相邻字符
for (int i = 0; i < groupSize && index < totalLen; i++) {
sb.append(charAllList.get(index));
index++;
}
groupList.add(sb.toString());
}
// 所有分组随机打乱
Collections.shuffle(groupList, random);
// 拼接输出
StringBuilder result = new StringBuilder();
for (String item : groupList) {
result.append(item);
}
tvContent.setText(result.toString());
}
public String getOriginText() {
return originText;
}
public void resetSeekBar() {
seekBarScale.setProgress(0);
}
}

View File

@@ -8,12 +8,20 @@
<ScrollView
android:layout_width="match_parent"
android:layout_height="500dp">
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:padding="10dp">
<!-- 新增自定义ProtectModeTextView -->
<cc.winboll.studio.mymessagemanager.views.ProtectModeTextView
android:id="@+id/protect_mode_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"/>
<LinearLayout
android:orientation="horizontal"

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:gravity="center"/>
<SeekBar
android:id="@+id/seek_bar_scale"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"/>
</LinearLayout>