短信发送窗口UI优化
This commit is contained in:
		@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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<SMSActivity> mActivity;  
 | 
			
		||||
		MyHandler(SMSActivity activity) {  
 | 
			
		||||
			mActivity = new WeakReference<SMSActivity>(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<SMSActivity> mActivity;
 | 
			
		||||
	 MyHandler(SMSActivity activity) {
 | 
			
		||||
	 mActivity = new WeakReference<SMSActivity>(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());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,125 @@
 | 
			
		||||
package cc.winboll.studio.mymessagemanager.views;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
 | 
			
		||||
 * @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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -10,36 +10,40 @@
 | 
			
		||||
		android:layout_height="@dimen/toolbar_height"
 | 
			
		||||
		android:id="@+id/activitysmsASupportToolbar1"/>
 | 
			
		||||
 | 
			
		||||
	<RelativeLayout
 | 
			
		||||
		android:orientation="vertical"
 | 
			
		||||
	<ScrollView
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="match_parent"
 | 
			
		||||
		android:paddingBottom="10dp">
 | 
			
		||||
		android:layout_height="0dp"
 | 
			
		||||
		android:layout_weight="1.0"
 | 
			
		||||
		android:id="@+id/activitysmsScrollView2">
 | 
			
		||||
 | 
			
		||||
		<ScrollView
 | 
			
		||||
			android:layout_alignParentTop="true"
 | 
			
		||||
			android:layout_above="@+id/activitysmsinclude1"
 | 
			
		||||
		<LinearLayout
 | 
			
		||||
			android:orientation="vertical"
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="0dp"
 | 
			
		||||
			android:id="@+id/activitysmsinphoneScrollView1"
 | 
			
		||||
			android:layout_weight="1.0"
 | 
			
		||||
			android:isScrollContainer="false">
 | 
			
		||||
			android:layout_height="match_parent"
 | 
			
		||||
			android:id="@+id/activitysmsLinearLayout1">
 | 
			
		||||
 | 
			
		||||
			<cc.winboll.studio.mymessagemanager.views.SMSListViewForScrollView
 | 
			
		||||
			<cc.winboll.studio.mymessagemanager.views.BottomPositionFixedScrollView
 | 
			
		||||
				android:layout_width="match_parent"
 | 
			
		||||
				android:layout_height="520dp"
 | 
			
		||||
				android:isScrollContainer="false"
 | 
			
		||||
				android:id="@+id/activitysmsScrollView1">
 | 
			
		||||
 | 
			
		||||
				<cc.winboll.studio.mymessagemanager.views.SMSListViewForScrollView
 | 
			
		||||
					android:layout_width="match_parent"
 | 
			
		||||
					android:layout_height="wrap_content"
 | 
			
		||||
					android:id="@+id/activitysmsSMSListViewForScrollView1"/>
 | 
			
		||||
 | 
			
		||||
			</cc.winboll.studio.mymessagemanager.views.BottomPositionFixedScrollView>
 | 
			
		||||
 | 
			
		||||
			<include
 | 
			
		||||
				layout="@layout/view_smssend"
 | 
			
		||||
				android:layout_width="match_parent"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:id="@+id/activitysmsinphoneListView1"/>
 | 
			
		||||
				android:id="@+id/activitysmsinclude1"/>
 | 
			
		||||
			
 | 
			
		||||
		</LinearLayout>
 | 
			
		||||
 | 
			
		||||
		</ScrollView>
 | 
			
		||||
 | 
			
		||||
		<include
 | 
			
		||||
			android:layout_alignParentBottom="true"
 | 
			
		||||
			layout="@layout/view_smssend"
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:id="@+id/activitysmsinclude1"/>
 | 
			
		||||
 | 
			
		||||
	</RelativeLayout>
 | 
			
		||||
	</ScrollView>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,18 @@
 | 
			
		||||
	android:layout_height="wrap_content"
 | 
			
		||||
	android:padding="10dp">
 | 
			
		||||
 | 
			
		||||
	<TextView
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:gravity="center"
 | 
			
		||||
		android:textSize="20sp"
 | 
			
		||||
		android:id="@+id/viewsmssendpart1TextView1"/>
 | 
			
		||||
	
 | 
			
		||||
	<cc.winboll.studio.libaes.views.AOHPCTCSeekBar
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:id="@+id/viewsmssendpart1AOHPCTCSeekBar1"/>
 | 
			
		||||
 | 
			
		||||
	<EditText
 | 
			
		||||
        android:scrollbars="vertical"
 | 
			
		||||
        android:maxHeight="150dp"
 | 
			
		||||
@@ -17,17 +29,6 @@
 | 
			
		||||
		android:id="@+id/viewsmssendpart1EditText1"
 | 
			
		||||
		android:background="@drawable/bg_frame"/>
 | 
			
		||||
 | 
			
		||||
	<cc.winboll.studio.libaes.views.AOHPCTCSeekBar
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:id="@+id/viewsmssendpart1AOHPCTCSeekBar1"/>
 | 
			
		||||
 | 
			
		||||
	<TextView
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:gravity="center"
 | 
			
		||||
		android:textSize="20sp"
 | 
			
		||||
		android:id="@+id/viewsmssendpart1TextView1"/>
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,6 @@
 | 
			
		||||
    <string name="text_appsettings">应用设置</string>
 | 
			
		||||
    <string name="text_ttsplaydelaytimes">TTS播放延迟时间(秒):</string>
 | 
			
		||||
    <string name="msg_newsms">接收到新的消息。</string>
 | 
			
		||||
    <string name="msg_100sendmsg">>>>拉图标动到 100% 以发送信息。>>></string>
 | 
			
		||||
    <string name="msg_100applysettings">>>>拉图标动到 100% 应用设置。>>></string>
 | 
			
		||||
    <string name="msg_100sendmsg">>>>拉动到100%可发信息>>></string>
 | 
			
		||||
    <string name="msg_100applysettings">>>>拉动到100%可应用设置>>></string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user