543 lines
20 KiB
Java
543 lines
20 KiB
Java
package cc.winboll.studio.libappbase;
|
||
|
||
/**
|
||
* @Author ZhanGSKen@QQ.COM
|
||
* @Date 2024/08/12 14:36:18
|
||
* @Describe 日志视图类,继承 RelativeLayout 类。
|
||
*/
|
||
import android.content.ClipData;
|
||
import android.content.ClipboardManager;
|
||
import android.content.Context;
|
||
import android.os.Handler;
|
||
import android.os.Message;
|
||
import android.text.Editable;
|
||
import android.text.TextWatcher;
|
||
import android.util.AttributeSet;
|
||
import android.view.LayoutInflater;
|
||
import android.view.View;
|
||
import android.view.ViewGroup;
|
||
import android.widget.AdapterView;
|
||
import android.widget.ArrayAdapter;
|
||
import android.widget.BaseAdapter;
|
||
import android.widget.CheckBox;
|
||
import android.widget.EditText;
|
||
import android.widget.HorizontalScrollView;
|
||
import android.widget.RelativeLayout;
|
||
import android.widget.ScrollView;
|
||
import android.widget.TextView;
|
||
import cc.winboll.studio.libappbase.LogUtils;
|
||
import cc.winboll.studio.libappbase.R;
|
||
import cc.winboll.studio.libappbase.views.HorizontalListView;
|
||
import cc.winboll.studio.libappbase.widget.LogTagSpinner;
|
||
import java.text.Collator;
|
||
import java.util.ArrayList;
|
||
import java.util.Collections;
|
||
import java.util.Comparator;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
|
||
public class LogView extends RelativeLayout {
|
||
|
||
public static final String TAG = "LogView";
|
||
|
||
public volatile boolean mIsHandling;
|
||
public volatile boolean mIsAddNewLog;
|
||
|
||
Context mContext;
|
||
ScrollView mScrollView;
|
||
TextView mTextView;
|
||
EditText metTagSearch;
|
||
CheckBox mSelectableCheckBox;
|
||
CheckBox mSelectAllTAGCheckBox;
|
||
TAGListAdapter mTAGListAdapter;
|
||
LogViewThread mLogViewThread;
|
||
LogViewHandler mLogViewHandler;
|
||
LogTagSpinner mLogLevelSpinner;
|
||
ArrayAdapter<CharSequence> mLogLevelSpinnerAdapter;
|
||
// 标签列表
|
||
HorizontalListView mListViewTags;
|
||
|
||
public LogView(Context context) {
|
||
super(context);
|
||
initView(context);
|
||
}
|
||
|
||
public LogView(Context context, AttributeSet attrs) {
|
||
super(context, attrs);
|
||
initView(context);
|
||
}
|
||
|
||
public LogView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||
super(context, attrs, defStyleAttr);
|
||
initView(context);
|
||
}
|
||
|
||
public LogView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||
super(context, attrs, defStyleAttr, defStyleRes);
|
||
initView(context);
|
||
}
|
||
|
||
public void start() {
|
||
mLogViewThread = new LogViewThread(LogView.this);
|
||
mLogViewThread.start();
|
||
// 显示日志
|
||
showAndScrollLogView();
|
||
}
|
||
|
||
public void scrollLogUp() {
|
||
mScrollView.post(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
|
||
// 日志显示结束
|
||
mLogViewHandler.setIsHandling(false);
|
||
// 检查是否添加了新日志
|
||
if (mLogViewHandler.isAddNewLog()) {
|
||
// 有新日志添加,先更改新日志标志
|
||
mLogViewHandler.setIsAddNewLog(false);
|
||
// 再次发送显示日志的显示
|
||
Message message = mLogViewHandler.obtainMessage(LogViewHandler.MSG_LOGVIEW_UPDATE);
|
||
mLogViewHandler.sendMessage(message);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
void initView(Context context) {
|
||
mContext = context;
|
||
mLogViewHandler = new LogViewHandler();
|
||
// 加载视图布局
|
||
addView(inflate(mContext, cc.winboll.studio.libappbase.R.layout.view_log, null));
|
||
// 初始化日志子控件视图
|
||
//
|
||
mScrollView = findViewById(cc.winboll.studio.libappbase.R.id.viewlogScrollViewLog);
|
||
mTextView = findViewById(cc.winboll.studio.libappbase.R.id.viewlogTextViewLog);
|
||
metTagSearch = findViewById(cc.winboll.studio.libappbase.R.id.tagsearch_et);
|
||
// 获取Log Level spinner实例
|
||
mLogLevelSpinner = findViewById(cc.winboll.studio.libappbase.R.id.viewlogSpinner1);
|
||
|
||
metTagSearch.setTextColor(mContext.getResources().getColor(R.color.white));
|
||
metTagSearch.addTextChangedListener(new TextWatcher() {
|
||
|
||
@Override
|
||
public void afterTextChanged(Editable editable) {
|
||
}
|
||
|
||
@Override
|
||
public void beforeTextChanged(CharSequence charSequence, int p, int p1, int p2) {
|
||
}
|
||
|
||
@Override
|
||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||
LogUtils.d(TAG, s.toString());
|
||
if (s.length() > 0) {
|
||
scrollToTag(s.toString());
|
||
} else {
|
||
HorizontalScrollView hsRoot = findViewById(R.id.viewlogHorizontalScrollView1);
|
||
hsRoot.smoothScrollTo(0, 0);
|
||
mListViewTags.resetScrollToStart();
|
||
}
|
||
// mListViewTags.postDelayed(new Runnable() {
|
||
// @Override
|
||
// public void run() {
|
||
// mListViewTags.scrollToItem(5);
|
||
// }
|
||
// }, 100);
|
||
}
|
||
// 其他方法留空或按需实现
|
||
});
|
||
|
||
|
||
(findViewById(cc.winboll.studio.libappbase.R.id.viewlogButtonClean)).setOnClickListener(new View.OnClickListener(){
|
||
|
||
@Override
|
||
public void onClick(View v) {
|
||
LogUtils.cleanLog();
|
||
LogUtils.d(TAG, "Log is cleaned.");
|
||
}
|
||
});
|
||
(findViewById(cc.winboll.studio.libappbase.R.id.viewlogButtonCopy)).setOnClickListener(new View.OnClickListener(){
|
||
|
||
@Override
|
||
public void onClick(View v) {
|
||
|
||
ClipboardManager cm = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||
cm.setPrimaryClip(ClipData.newPlainText(mContext.getPackageName(), LogUtils.loadLog()));
|
||
LogUtils.d(TAG, "Log is copied.");
|
||
}
|
||
});
|
||
mSelectableCheckBox = findViewById(cc.winboll.studio.libappbase.R.id.viewlogCheckBoxSelectable);
|
||
mSelectableCheckBox.setOnClickListener(new View.OnClickListener(){
|
||
@Override
|
||
public void onClick(View v) {
|
||
if (mSelectableCheckBox.isChecked()) {
|
||
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
|
||
} else {
|
||
setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
||
}
|
||
}
|
||
});
|
||
|
||
// 设置日志级别列表
|
||
// ArrayList<String> adapterItems = new ArrayList<>();
|
||
// for (LogUtils.LOG_LEVEL e : LogUtils.LOG_LEVEL.values()) {
|
||
// adapterItems.add(e.name());
|
||
// }
|
||
String[] mLogLevelSpinnerData = new String[LogUtils.LOG_LEVEL.values().length];
|
||
for (int i = 0; i < LogUtils.LOG_LEVEL.values().length; i++) {
|
||
mLogLevelSpinnerData[i] = LogUtils.LOG_LEVEL.values()[i].name();
|
||
}
|
||
mLogLevelSpinner.setLogTagData(mLogLevelSpinnerData);
|
||
// 假设你有一个字符串数组作为选项列表
|
||
//String[] options = {"Option 1", "Option 2", "Option 3"};
|
||
// 创建一个ArrayAdapter来绑定数据到spinner
|
||
// mLogLevelSpinnerAdapter = ArrayAdapter.createFromResource(
|
||
// context, cc.winboll.studio.libappbase.R.array.enum_loglevel_array, android.R.layout.simple_spinner_item);
|
||
// mLogLevelSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||
|
||
// // 设置适配器并将它应用到spinner上
|
||
// mLogLevelSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // 设置下拉视图样式
|
||
// mLogLevelSpinner.setAdapter(mLogLevelSpinnerAdapter);
|
||
// 为Spinner添加监听器
|
||
mLogLevelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||
@Override
|
||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||
//String selectedOption = mLogLevelSpinnerAdapter.getItem(position);
|
||
// 处理选中的选项...
|
||
LogUtils.setLogLevel(LogUtils.LOG_LEVEL.values()[position]);
|
||
}
|
||
@Override
|
||
public void onNothingSelected(AdapterView<?> parent) {
|
||
// 如果没有选择,则执行此操作...
|
||
}
|
||
});
|
||
// 获取默认值的索引
|
||
int defaultValueIndex = LogUtils.getLogLevel().ordinal();
|
||
|
||
if (defaultValueIndex != -1) {
|
||
// 如果找到了默认值,设置默认选项
|
||
mLogLevelSpinner.setSelection(defaultValueIndex);
|
||
}
|
||
|
||
// 加载标签列表
|
||
Map<String, Boolean> mapTAGList = LogUtils.getMapTAGList();
|
||
boolean isAllSelect = true;
|
||
for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) {
|
||
if (entry.getValue() == false) {
|
||
isAllSelect = false;
|
||
break;
|
||
}
|
||
}
|
||
CheckBox cbALLTAG = findViewById(cc.winboll.studio.libappbase.R.id.viewlogCheckBox1);
|
||
cbALLTAG.setChecked(isAllSelect);
|
||
|
||
// 加载标签表
|
||
mListViewTags = findViewById(cc.winboll.studio.libappbase.R.id.tags_listview);
|
||
mListViewTags.setVerticalOffset(10);
|
||
mTAGListAdapter = new TAGListAdapter(mContext, mapTAGList);
|
||
mListViewTags.setAdapter(mTAGListAdapter);
|
||
|
||
// 可以添加点击监听器来处理勾选框状态变化后的逻辑,比如获取当前勾选情况等
|
||
mTAGListAdapter.notifyDataSetChanged();
|
||
|
||
mSelectAllTAGCheckBox = findViewById(cc.winboll.studio.libappbase.R.id.viewlogCheckBox1);
|
||
ViewGroup.LayoutParams layoutParams2 = mSelectAllTAGCheckBox.getLayoutParams();
|
||
if (layoutParams2 != null) {
|
||
layoutParams2.width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||
layoutParams2.height = 75;
|
||
}
|
||
mSelectAllTAGCheckBox.setLayoutParams(layoutParams2);
|
||
//mSelectAllTAGCheckBox.setPadding(0,0,0,0);
|
||
mSelectAllTAGCheckBox.setTextColor(mContext.getResources().getColor(R.color.white));
|
||
mSelectAllTAGCheckBox.setOnClickListener(new View.OnClickListener(){
|
||
@Override
|
||
public void onClick(View v) {
|
||
LogUtils.setALlTAGListEnable(mSelectAllTAGCheckBox.isChecked());
|
||
//LogUtils.setALlTAGListEnable(false);
|
||
//mTAGListAdapter.notifyDataSetChanged();
|
||
mTAGListAdapter.reload();
|
||
//ToastUtils.show(String.format("onClick\nmSelectAllTAGCheckBox.isChecked() : %s", mSelectAllTAGCheckBox.isChecked()));
|
||
}
|
||
});
|
||
|
||
|
||
mLogLevelSpinner.updateTextSize(R.dimen.log_spinner_text_size);
|
||
|
||
// 设置滚动时不聚焦日志
|
||
setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
||
}
|
||
|
||
public void updateLogView() {
|
||
if (mLogViewHandler.isHandling() == true) {
|
||
// 正在处理日志显示,
|
||
// 就先设置一个新日志标志位
|
||
// 以便日志显示完后,再次显示新日志内容
|
||
mLogViewHandler.setIsAddNewLog(true);
|
||
} else {
|
||
//LogUtils.d(TAG, "LogListener showLog(String path)");
|
||
Message message = mLogViewHandler.obtainMessage(LogViewHandler.MSG_LOGVIEW_UPDATE);
|
||
mLogViewHandler.sendMessage(message);
|
||
mLogViewHandler.setIsAddNewLog(false);
|
||
}
|
||
}
|
||
|
||
void showAndScrollLogView() {
|
||
mTextView.setText(LogUtils.loadLog());
|
||
scrollLogUp();
|
||
}
|
||
|
||
public void scrollToTag(final String prefix) {
|
||
if (mTAGListAdapter == null || prefix == null || prefix.length() == 0) {
|
||
LogUtils.d(TAG, "参数为空,无法滚动");
|
||
return;
|
||
}
|
||
|
||
final List<TAGItemModel> itemList = mTAGListAdapter.getItemList();
|
||
|
||
mListViewTags.post(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
// 查找匹配的标签位置
|
||
int targetPosition = -1;
|
||
|
||
for (int i = 0; i < itemList.size(); i++) {
|
||
String tag = itemList.get(i).getTag();
|
||
if (tag != null && tag.toLowerCase().startsWith(prefix.toLowerCase())) {
|
||
targetPosition = i;
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (targetPosition != -1) {
|
||
// 优化滚动逻辑
|
||
//mListViewTags.setSelection(targetPosition);
|
||
//mListViewTags.invalidateViews(); // 强制刷新所有可见项
|
||
|
||
// 单独刷新目标视图
|
||
// View targetView = mListViewTags.getChildAt(targetPosition);
|
||
// if (targetView != null) {
|
||
// targetView.requestLayout();
|
||
// targetView.requestFocus();
|
||
// }
|
||
|
||
final int scrollPosition = targetPosition;
|
||
|
||
// 延迟滚动确保布局完成
|
||
mListViewTags.postDelayed(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
LogUtils.d(TAG, String.format("scrollPosition %d", scrollPosition));
|
||
mListViewTags.scrollToItem(scrollPosition);
|
||
}
|
||
}, 100);
|
||
} else {
|
||
LogUtils.d(TAG, "未找到匹配的标签前缀:" + prefix);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
|
||
class LogViewHandler extends Handler {
|
||
|
||
final static int MSG_LOGVIEW_UPDATE = 0;
|
||
volatile boolean isHandling;
|
||
volatile boolean isAddNewLog;
|
||
|
||
public LogViewHandler() {
|
||
setIsHandling(false);
|
||
setIsAddNewLog(false);
|
||
}
|
||
|
||
public void setIsHandling(boolean isHandling) {
|
||
this.isHandling = isHandling;
|
||
}
|
||
|
||
public boolean isHandling() {
|
||
return isHandling;
|
||
}
|
||
|
||
public void setIsAddNewLog(boolean isAddNewLog) {
|
||
this.isAddNewLog = isAddNewLog;
|
||
}
|
||
|
||
public boolean isAddNewLog() {
|
||
return isAddNewLog;
|
||
}
|
||
|
||
public void handleMessage(Message msg) {
|
||
switch (msg.what) {
|
||
case MSG_LOGVIEW_UPDATE:{
|
||
if (isHandling() == false) {
|
||
setIsHandling(true);
|
||
showAndScrollLogView();
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
super.handleMessage(msg);
|
||
}
|
||
}
|
||
|
||
public class TAGItemModel {
|
||
private String tag;
|
||
private boolean isChecked;
|
||
|
||
public TAGItemModel(String tag, boolean isChecked) {
|
||
this.tag = tag;
|
||
this.isChecked = isChecked;
|
||
}
|
||
|
||
public String getTag() {
|
||
return tag;
|
||
}
|
||
|
||
public void setTag(String tag) {
|
||
this.tag = tag;
|
||
}
|
||
|
||
public boolean isChecked() {
|
||
return isChecked;
|
||
}
|
||
|
||
public void setChecked(boolean checked) {
|
||
isChecked = checked;
|
||
}
|
||
|
||
// getter/setter...
|
||
|
||
@Override
|
||
public boolean equals(Object o) {
|
||
if (this == o) {
|
||
return true;
|
||
}
|
||
if (o == null || getClass() != o.getClass()) {
|
||
return false;
|
||
}
|
||
TAGItemModel that = (TAGItemModel) o;
|
||
// 手动处理空值比较(Java 6 不支持 Objects.equals)
|
||
if (tag == null) {
|
||
return that.tag == null;
|
||
} else {
|
||
return tag.equals(that.tag);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public int hashCode() {
|
||
return tag == null ? 0 : tag.hashCode(); // 手动处理空值
|
||
}
|
||
}
|
||
|
||
|
||
public class TAGListAdapter extends BaseAdapter {
|
||
|
||
private Context context;
|
||
private Map<String, Boolean> mapOrigin;
|
||
private List<TAGItemModel> itemList;
|
||
|
||
public TAGListAdapter(Context context, Map<String, Boolean> map) {
|
||
this.context = context;
|
||
mapOrigin = map;
|
||
loadMap(mapOrigin);
|
||
}
|
||
|
||
public List<TAGItemModel> getItemList() {
|
||
return itemList;
|
||
}
|
||
|
||
@Override
|
||
public int getCount() {
|
||
return itemList.size();
|
||
}
|
||
|
||
@Override
|
||
public Object getItem(int p) {
|
||
return itemList.get(p);
|
||
}
|
||
|
||
@Override
|
||
public long getItemId(int p) {
|
||
return p;
|
||
}
|
||
|
||
void loadMap(Map<String, Boolean> map) {
|
||
itemList = new ArrayList<TAGItemModel>();
|
||
for (Map.Entry<String, Boolean> entry : map.entrySet()) {
|
||
itemList.add(new TAGItemModel(entry.getKey(), entry.getValue()));
|
||
}
|
||
// 添加排序功能,按照tag进行升序排序
|
||
Collections.sort(itemList, new SortMapEntryByKeyString(true));
|
||
//Collections.sort(itemList, new SortMapEntryByKeyString(false));
|
||
}
|
||
|
||
public void reload() {
|
||
loadMap(mapOrigin);
|
||
super.notifyDataSetChanged();
|
||
}
|
||
|
||
@Override
|
||
public View getView(int position, View convertView, ViewGroup parent) {
|
||
ViewHolder holder;
|
||
if (convertView == null) {
|
||
convertView = LayoutInflater.from(context).inflate(R.layout.item_logtag, parent, false);
|
||
holder = new ViewHolder();
|
||
holder.tvText = convertView.findViewById(R.id.viewlogtagTextView1);
|
||
holder.cbChecked = convertView.findViewById(R.id.viewlogtagCheckBox1);
|
||
convertView.setTag(holder);
|
||
} else {
|
||
holder = (ViewHolder) convertView.getTag();
|
||
}
|
||
|
||
final TAGItemModel item = itemList.get(position);
|
||
holder.tvText.setText(item.getTag());
|
||
ViewGroup.LayoutParams layoutParams = holder.tvText.getLayoutParams();
|
||
if (layoutParams != null) {
|
||
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||
layoutParams.height = 75;
|
||
}
|
||
holder.tvText.setLayoutParams(layoutParams);
|
||
holder.tvText.setPadding(0,0,0,0);
|
||
holder.tvText.setTextColor(mContext.getResources().getColor(R.color.white));
|
||
holder.cbChecked.setChecked(item.isChecked());
|
||
holder.cbChecked.setLayoutParams(layoutParams);
|
||
holder.cbChecked.setPadding(0,0,0,0);
|
||
holder.cbChecked.setOnClickListener(new View.OnClickListener(){
|
||
|
||
@Override
|
||
public void onClick(View v) {
|
||
LogUtils.setTAGListEnable(item.getTag(), ((CheckBox)v).isChecked());
|
||
}
|
||
});
|
||
|
||
return convertView;
|
||
}
|
||
|
||
public class ViewHolder {
|
||
TextView tvText;
|
||
CheckBox cbChecked;
|
||
}
|
||
}
|
||
|
||
class SortMapEntryByKeyString implements Comparator<TAGItemModel> {
|
||
private boolean mIsDesc = true;
|
||
// isDesc 是否降序排列
|
||
public SortMapEntryByKeyString(boolean isDesc) {
|
||
mIsDesc = isDesc;
|
||
}
|
||
Collator cmp = Collator.getInstance(java.util.Locale.CHINA);
|
||
@Override
|
||
public int compare(TAGItemModel o1, TAGItemModel o2) {
|
||
if (mIsDesc) {
|
||
return o1.getTag().compareTo(o2.getTag());
|
||
} else {
|
||
return o2.getTag().compareTo(o1.getTag());
|
||
}
|
||
}
|
||
}
|
||
}
|