diff --git a/mymessagemanager/build.properties b/mymessagemanager/build.properties index 63e9af8..5d39d10 100644 --- a/mymessagemanager/build.properties +++ b/mymessagemanager/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Thu Jul 03 13:50:15 HKT 2025 +#Sat Aug 23 05:36:15 GMT 2025 stageCount=2 libraryProject= baseVersion=15.3 publishVersion=15.3.1 -buildCount=0 +buildCount=57 baseBetaVersion=15.3.2 diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSActivity.java index 0cd570e..41c276f 100644 --- a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSActivity.java +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSActivity.java @@ -4,11 +4,16 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.inputmethod.InputMethodManager; import android.widget.AbsListView; import android.widget.EditText; +import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; @@ -16,19 +21,17 @@ import android.widget.Toolbar; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; import cc.winboll.studio.mymessagemanager.R; -import cc.winboll.studio.mymessagemanager.activitys.SMSActivity; import cc.winboll.studio.mymessagemanager.adapters.SMSArrayAdapter; import cc.winboll.studio.mymessagemanager.utils.AddressUtils; import cc.winboll.studio.mymessagemanager.utils.SMSUtil; import cc.winboll.studio.mymessagemanager.utils.ViewUtil; +import cc.winboll.studio.mymessagemanager.views.BottomPositionFixedScrollView; import cc.winboll.studio.mymessagemanager.views.SMSListViewForScrollView; import java.lang.ref.WeakReference; public class SMSActivity extends BaseActivity { public static String TAG = "SMSActivity"; - public static final String ACTION_NOTIFY_SMS_CHANGED = "cc.winboll.studio.mymessagemanager.activitys.SMSActivity.ACTION_NOTIFY_SMS_CHANGED"; - public static final String EXTRA_PHONE = "Phone"; final static int MSG_SET_FOCUS = 0; @@ -36,10 +39,11 @@ public class SMSActivity extends BaseActivity { Toolbar mToolbar; String mszPhoneTo; SMSArrayAdapter mSMSArrayAdapter; - ScrollView mScrollView; + BottomPositionFixedScrollView mScrollView1; EditText metSMSBody; SMSActivityBroadcastReceiver mSMSActivityBroadcastReceiver; Handler mSetFocusHandler; + private boolean isImeVisible = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -47,45 +51,90 @@ public class SMSActivity extends BaseActivity { setContentView(R.layout.activity_sms); initView(); - mSetFocusHandler = new MyHandler(SMSActivity.this); scrollScrollView(); + setupImeStatusListener(); - // 每隔一定时间设置输入框获得焦点 - // - new Thread() { - @Override - public void run() { - while (true) { - try { - Thread.sleep(1500); - } catch (InterruptedException e) {} - Message message = mSetFocusHandler.obtainMessage(MSG_SET_FOCUS); - mSetFocusHandler.sendMessage(message); - } - }}.start(); + // 新增:监听窗口加载完成,触发mScrollView1滚动到底部 + setupScrollToBottomAfterWindowLoaded(); } - // - // 设置输入框获得焦点的类 - // - static class MyHandler extends Handler { - WeakReference mActivity; - MyHandler(SMSActivity activity) { - mActivity = new WeakReference(activity); - } - public void handleMessage(Message msg) { - SMSActivity theActivity = mActivity.get(); - switch (msg.what) { - case MSG_SET_FOCUS: - theActivity.metSMSBody.setFocusable(true); - theActivity.metSMSBody.requestFocus(); - break; - default: - break; - } - super.handleMessage(msg); - } - } + // 新增:窗口加载完成后让mScrollView1滚动到底部 + private void setupScrollToBottomAfterWindowLoaded() { + final View rootView = findViewById(android.R.id.content); + // 监听根布局绘制完成(窗口加载完成的标志) + rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // 滚动到底部 + mScrollView1.post(new Runnable() { + @Override + public void run() { + mScrollView1.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + + // 移除监听,避免重复触发 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } else { + rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + } + }); + } + + private void setupImeStatusListener() { + final View rootView = findViewById(android.R.id.content); + rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + int rootViewHeight = rootView.getHeight(); + int screenHeight = getResources().getDisplayMetrics().heightPixels; + int imeThreshold = dp2px(200); + + boolean currentImeVisible = (screenHeight - rootViewHeight) > imeThreshold; + + if (currentImeVisible != isImeVisible) { + isImeVisible = currentImeVisible; + setupScrollView1Height(); + if (!isImeVisible) { + metSMSBody.clearFocus(); + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } else { + rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + setupImeStatusListener(); + } + }); + } + + private int dp2px(int dp) { + return (int) (dp * getResources().getDisplayMetrics().density + 0.5f); + } + + /*static class MyHandler extends Handler { + WeakReference mActivity; + MyHandler(SMSActivity activity) { + mActivity = new WeakReference(activity); + } + public void handleMessage(Message msg) { + SMSActivity theActivity = mActivity.get(); + switch (msg.what) { + case MSG_SET_FOCUS: + theActivity.metSMSBody.setFocusable(true); + theActivity.metSMSBody.requestFocus(); + theActivity.setupScrollView1Height(); + break; + default: + break; + } + super.handleMessage(msg); + } + }*/ @Override protected void onDestroy() { @@ -94,135 +143,130 @@ public class SMSActivity extends BaseActivity { } void initView() { - // 发送端空号码退出 mszPhoneTo = getIntent().getStringExtra(EXTRA_PHONE); if (mszPhoneTo == null || mszPhoneTo.trim().equals("")) { finish(); } - // 初始化标题栏 - mToolbar = findViewById(R.id.activitysmsASupportToolbar1); + mToolbar = (Toolbar) findViewById(R.id.activitysmsASupportToolbar1); mToolbar.setSubtitle(getString(R.string.activity_name_smsinphone) + " < Phone : " + AddressUtils.getFormattedAddress(mszPhoneTo) + " >"); setActionBar(mToolbar); - // 初始化滚动窗口 - mScrollView = findViewById(R.id.activitysmsinphoneScrollView1); + mScrollView1 = (BottomPositionFixedScrollView) findViewById(R.id.activitysmsScrollView1); - // 初始化发送消息框 - //Drawable drawableFrame = AppCompatResources.getDrawable(this, R.drawable.bg_frame); - metSMSBody = findViewById(R.id.viewsmssendpart1EditText1); - //metSMSBody.setBackground(drawableFrame); + metSMSBody = (EditText) findViewById(R.id.viewsmssendpart1EditText1); + metSMSBody.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setupScrollView1Height(); + } + }); + metSMSBody.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + setupScrollView1Height(); + } + }); - // 初始化发送拉动控件 - final AOHPCTCSeekBar aOHPCTCSeekBar = findViewById(R.id.viewsmssendpart1AOHPCTCSeekBar1); + final AOHPCTCSeekBar aOHPCTCSeekBar = (AOHPCTCSeekBar) findViewById(R.id.viewsmssendpart1AOHPCTCSeekBar1); aOHPCTCSeekBar.setThumb(getDrawable(R.drawable.ic_message)); aOHPCTCSeekBar.setThumbOffset(20); - aOHPCTCSeekBar.setOnOHPCListener( - new AOHPCTCSeekBar.OnOHPCListener(){ - @Override - public void onOHPCommit() { - //Toast.makeText(getApplication(), "Send", Toast.LENGTH_SHORT).show(); - sendSMS(); - } - }); + aOHPCTCSeekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener() { + @Override + public void onOHPCommit() { + sendSMS(); + } + }); - // 初始化提示框 - TextView tvAOHPCTCSeekBarMSG = findViewById(R.id.viewsmssendpart1TextView1); + TextView tvAOHPCTCSeekBarMSG = (TextView) findViewById(R.id.viewsmssendpart1TextView1); tvAOHPCTCSeekBarMSG.setText(R.string.msg_100sendmsg); - mlvSMS = (SMSListViewForScrollView) findViewById(R.id.activitysmsinphoneListView1); - - // 准备数据 + mlvSMS = (SMSListViewForScrollView) findViewById(R.id.activitysmsSMSListViewForScrollView1); mSMSArrayAdapter = new SMSArrayAdapter(SMSActivity.this, mszPhoneTo); mlvSMS.setAdapter(mSMSArrayAdapter); - // 设置短信列表滚动到底部就取消已发送的通知消息 - // mlvSMS.setOnScrollListener(new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - } + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) {} - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) { - // 滑动到了底部 - mSMSArrayAdapter.cancelMessageNotification(); - } - } - }); + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) { + mSMSArrayAdapter.cancelMessageNotification(); + } + } + }); mSMSActivityBroadcastReceiver = new SMSActivityBroadcastReceiver(); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ACTION_NOTIFY_SMS_CHANGED); + IntentFilter intentFilter = new IntentFilter(ACTION_NOTIFY_SMS_CHANGED); LocalBroadcastManager.getInstance(this).registerReceiver(mSMSActivityBroadcastReceiver, intentFilter); - - /*SMSView mSMSView = findViewById(R.id.viewsmssendSMSView1); - mSMSView.setSMSType(SMSView.SMSType.SEND);*/ } - // - // 更新信息列表 - // + private void setupScrollView1Height() { + mScrollView1.postDelayed(new Runnable() { + @Override + public void run() { + final ScrollView scrollView2 = (ScrollView) findViewById(R.id.activitysmsScrollView2); + final BottomPositionFixedScrollView scrollView1 = (BottomPositionFixedScrollView) findViewById(R.id.activitysmsScrollView1); + final View includeView = findViewById(R.id.activitysmsinclude1); + + scrollView2.post(new Runnable() { + @Override + public void run() { + int scrollView2Height = scrollView2.getHeight(); + int includeHeight = includeView.getHeight(); + int targetHeight = Math.max(scrollView2Height - includeHeight, 0); + + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) scrollView1.getLayoutParams(); + params.height = targetHeight; + scrollView1.setLayoutParams(params); + } + }); + } + }, 100); + } + public void updateSMSView() { mSMSArrayAdapter.reLoadSMSList(SMSActivity.this, mszPhoneTo); mSMSArrayAdapter.notifyDataSetChanged(); } - // - // 滚动消息文本框 - // void scrollScrollView() { - - ViewUtil.scrollScrollView(mScrollView); - + ViewUtil.scrollScrollView(mScrollView1); } - // - // 发送短信 - // void sendSMS() { - // 空消息不发送 String szSMSBody = metSMSBody.getText().toString(); if (szSMSBody.equals("")) { Toast.makeText(getApplication(), "没有消息内容可发送。", Toast.LENGTH_SHORT).show(); return; } - // 发送短信 if (SMSUtil.sendMessageByInterface2(this, mszPhoneTo, szSMSBody)) { metSMSBody.setText(""); - new Handler().postDelayed(new Runnable(){ - @Override - public void run() { - updateSMSView(); - ViewUtil.scrollScrollView(mScrollView); - } - }, 1000); + metSMSBody.clearFocus(); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + updateSMSView(); + ViewUtil.scrollScrollView(mScrollView1); + } + }, 1000); } } class SMSActivityBroadcastReceiver extends BroadcastReceiver { - - public SMSActivityBroadcastReceiver() { - //LogUtils.d(TAG, "SMSActivityBroadcastReceiver()"); - } + public SMSActivityBroadcastReceiver() {} @Override public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case ACTION_NOTIFY_SMS_CHANGED : - //Toast.makeText(context, "ACTION_NOTIFY_SMS_CHANGED", Toast.LENGTH_SHORT).show(); - updateSMSView(); - ViewUtil.scrollScrollView(mScrollView); - //LogUtils.d(TAG, "ACTION_NOTIFY_SMS_CHANGED"); - break; - default: - throw new IllegalStateException("Unexpected value: " + intent.getAction()); + if (ACTION_NOTIFY_SMS_CHANGED.equals(intent.getAction())) { + updateSMSView(); + ViewUtil.scrollScrollView(mScrollView1); + } else { + throw new IllegalStateException("Unexpected value: " + intent.getAction()); } - } - } - } + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/BottomPositionFixedScrollView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/BottomPositionFixedScrollView.java new file mode 100644 index 0000000..4c31b0e --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/BottomPositionFixedScrollView.java @@ -0,0 +1,125 @@ +package cc.winboll.studio.mymessagemanager.views; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/08/23 00:39 + * @Describe 多级拉动响应自定义控件 + */ +import android.content.Context; +import android.util.AttributeSet; +import android.view.ViewTreeObserver; +import android.widget.ScrollView; + +public class BottomPositionFixedScrollView extends ScrollView { + public static final String TAG = "BottomPositionFixedScrollView"; + // 记录底部对应的内容绝对位置(即底部位置在内容中的y坐标,该位置需始终保持在视图底部) + private int mBottomContentY = 0; + // 标记是否是首次布局(避免初始加载误触发) + private boolean isFirstLayout = true; + + public BottomPositionFixedScrollView(Context context) { + super(context); + init(); + } + + public BottomPositionFixedScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public BottomPositionFixedScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + // 监听布局变化(高度改变时触发) + getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (isFirstLayout) { + isFirstLayout = false; + return; + } + // 布局变化后,恢复底部位置 + restoreBottomPosition(); + } + }); + } + + /** + * 重写滚动事件,记录“底部对应的内容绝对位置” + * (即当前视图底部边缘对应的内容y坐标,该坐标需始终保持在视图底部) + */ + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + if (getChildCount() == 0) { + mBottomContentY = 0; + return; + } + + // 内容总高度 + int contentHeight = getChildAt(0).getMeasuredHeight(); + // 视图可视高度(自身高度) + int scrollViewHeight = getMeasuredHeight(); + // 当前视图底部边缘对应的内容y坐标 = 顶部滚动距离(t) + 可视高度 + // (该坐标就是“底部内容的绝对位置”,需始终保持在视图底部) + mBottomContentY = t + scrollViewHeight; + + // 避免超过内容总高度(比如内容不足一屏时,底部最多到内容底部) + if (mBottomContentY > contentHeight) { + mBottomContentY = contentHeight; + } + } + + /** + * 恢复底部位置:让原记录的“底部内容绝对位置”仍保持在视图底部 + */ + private void restoreBottomPosition() { + if (getChildCount() == 0) { + return; + } + + // 新的内容总高度 + int newContentHeight = getChildAt(0).getMeasuredHeight(); + // 新的视图可视高度 + int newScrollViewHeight = getMeasuredHeight(); + + // 目标:让原mBottomContentY(底部内容绝对位置)仍位于视图底部 + // 此时需要的顶部滚动距离 = mBottomContentY - 新的可视高度 + int targetScrollY = mBottomContentY - newScrollViewHeight; + + // 边界修正: + // 1. 不能小于0(避免滚动到负数位置) + // 2. 不能大于“最大可滚动距离”(内容高度 - 可视高度,避免超出内容范围) + int maxScrollY = Math.max(newContentHeight - newScrollViewHeight, 0); + targetScrollY = Math.max(targetScrollY, 0); + targetScrollY = Math.min(targetScrollY, maxScrollY); + + // 滚动到目标位置,保持底部内容位置不变 + smoothScrollTo(0, targetScrollY); + } + + /** + * 外部手动设置底部内容绝对位置(可选) + */ + public void setBottomContentY(int bottomContentY) { + if (getChildCount() == 0) { + mBottomContentY = bottomContentY; + return; + } + // 限制不超过内容总高度 + int contentHeight = getChildAt(0).getMeasuredHeight(); + mBottomContentY = Math.min(bottomContentY, contentHeight); + restoreBottomPosition(); + } + + /** + * 获取当前底部内容绝对位置(可选) + */ + public int getBottomContentY() { + return mBottomContentY; + } +} + diff --git a/mymessagemanager/src/main/res/layout/activity_sms.xml b/mymessagemanager/src/main/res/layout/activity_sms.xml index cf27fd4..a35f500 100644 --- a/mymessagemanager/src/main/res/layout/activity_sms.xml +++ b/mymessagemanager/src/main/res/layout/activity_sms.xml @@ -10,36 +10,40 @@ android:layout_height="@dimen/toolbar_height" android:id="@+id/activitysmsASupportToolbar1"/> - + android:layout_height="0dp" + android:layout_weight="1.0" + android:id="@+id/activitysmsScrollView2"> - + android:layout_height="match_parent" + android:id="@+id/activitysmsLinearLayout1"> - + + + + + + + android:id="@+id/activitysmsinclude1"/> + + - - - - - + diff --git a/mymessagemanager/src/main/res/layout/view_smssend_part1.xml b/mymessagemanager/src/main/res/layout/view_smssend_part1.xml index 4d48375..901efa9 100644 --- a/mymessagemanager/src/main/res/layout/view_smssend_part1.xml +++ b/mymessagemanager/src/main/res/layout/view_smssend_part1.xml @@ -7,6 +7,18 @@ android:layout_height="wrap_content" android:padding="10dp"> + + + + - - - - + diff --git a/mymessagemanager/src/main/res/values-zh/strings.xml b/mymessagemanager/src/main/res/values-zh/strings.xml index 407b1d8..d1acfc2 100644 --- a/mymessagemanager/src/main/res/values-zh/strings.xml +++ b/mymessagemanager/src/main/res/values-zh/strings.xml @@ -41,6 +41,6 @@ 应用设置 TTS播放延迟时间(秒): 接收到新的消息。 - >>>拉图标动到 100% 以发送信息。>>> - >>>拉图标动到 100% 应用设置。>>> + >>>拉动到100%可发信息>>> + >>>拉动到100%可应用设置>>>