From fb2456b0169a9c30109354146b4ea40dbdaaea64 Mon Sep 17 00:00:00 2001 From: wufanghan Date: Fri, 5 Mar 2021 11:49:45 +0800 Subject: [PATCH] add inputview --- app/build.gradle | 4 +- .../baseframe/utils/SoftInputUtils.java | 143 +++++++ .../example/baseframe/weight/InputView.java | 360 ++++++++++++++++++ .../uploadImageList/UploadImgListBuilder.java | 65 ++++ .../UploadImgListContract.java | 12 + .../layout/layout_input_has_select_image.xml | 73 ++++ build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 8 files changed, 659 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/example/baseframe/utils/SoftInputUtils.java create mode 100644 app/src/main/java/com/example/baseframe/weight/InputView.java create mode 100644 app/src/main/java/com/example/baseframe/weight/uploadImageList/UploadImgListBuilder.java create mode 100644 app/src/main/java/com/example/baseframe/weight/uploadImageList/UploadImgListContract.java create mode 100644 app/src/main/res/layout/layout_input_has_select_image.xml diff --git a/app/build.gradle b/app/build.gradle index 129eeb6..9780718 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,7 +54,7 @@ dependencies { implementation 'androidx.multidex:multidex:2.0.1' //permissions - implementation 'com.github.tbruyelle:rxpermissions:0.11' + implementation 'com.github.tbruyelle:rxpermissions:0.12' //rx implementation 'io.reactivex.rxjava3:rxjava:3.0.4' @@ -93,4 +93,6 @@ dependencies { //滴滴的调试插件 https://github.com/didi/DoraemonKit debugImplementation 'com.didichuxing.doraemonkit:doraemonkit:3.1.4' releaseImplementation 'com.didichuxing.doraemonkit:doraemonkit-no-op:2.0.2' + + api 'com.github.LuckSiege.PictureSelector:picture_library:v2.5.9' } diff --git a/app/src/main/java/com/example/baseframe/utils/SoftInputUtils.java b/app/src/main/java/com/example/baseframe/utils/SoftInputUtils.java new file mode 100644 index 0000000..6fc92c4 --- /dev/null +++ b/app/src/main/java/com/example/baseframe/utils/SoftInputUtils.java @@ -0,0 +1,143 @@ +package com.example.baseframe.utils; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +/** + * private void attachView() { + * + * + * //editText2为需要调整的View + * editText2 = findViewById(R.id.et2); + * SoftInputUtil softInputUtil = new SoftInputUtil(); + * softInputUtil.attachSoftInput(editText2, new SoftInputUtil.ISoftInputChanged() { + * @Override + * public void onChanged(boolean isSoftInputShow, int softInputHeight, int viewOffset) { + * if (isSoftInputShow) { + * editText2.setTranslationY(et2.getTranslationY() - viewOffset); + * } else { + * editText2.setTranslationY(0); + * } + * } + * }); + * } + * + * android:windowSoftInputMode="adjustResize|stateAlwaysHidden" + */ +public class SoftInputUtils { + private int softInputHeight = 0; + private boolean softInputHeightChanged = false; + + private boolean isNavigationBarShow = false; + private int navigationHeight = 0; + + private View anyView; + private ISoftInputChanged listener; + private boolean isSoftInputShowing = false; + + public interface ISoftInputChanged { + void onChanged(boolean isSoftInputShow, int softInputHeight, int viewOffset); + } + + public void attachSoftInput(final View anyView, final ISoftInputChanged listener) { + if (anyView == null || listener == null) + return; + + //根View + final View rootView = anyView.getRootView(); + if (rootView == null) + return; + + navigationHeight = getNavigationBarHeight(anyView.getContext()); + + //anyView为需要调整高度的View,理论上来说可以是任意的View + this.anyView = anyView; + this.listener = listener; + + rootView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + //对于Activity来说,该高度即为屏幕高度 + int rootHeight = rootView.getHeight(); + Rect rect = new Rect(); + //获取当前可见部分,默认可见部分是除了状态栏和导航栏剩下的部分 + rootView.getWindowVisibleDisplayFrame(rect); + + if (rootHeight - rect.bottom == navigationHeight) { + //如果可见部分底部与屏幕底部刚好相差导航栏的高度,则认为有导航栏 + isNavigationBarShow = true; + } else if (rootHeight - rect.bottom == 0) { + //如果可见部分底部与屏幕底部平齐,说明没有导航栏 + isNavigationBarShow = false; + } + + //cal softInput height + boolean isSoftInputShow = false; + int softInputHeight = 0; + //如果有导航栏,则要去除导航栏的高度 + int mutableHeight = isNavigationBarShow == true ? navigationHeight : 0; + if (rootHeight - mutableHeight > rect.bottom) { + //除去导航栏高度后,可见区域仍然小于屏幕高度,则说明键盘弹起了 + isSoftInputShow = true; + //键盘高度 + softInputHeight = rootHeight - mutableHeight - rect.bottom; + if (SoftInputUtils.this.softInputHeight != softInputHeight) { + softInputHeightChanged = true; + SoftInputUtils.this.softInputHeight = softInputHeight; + } else { + softInputHeightChanged = false; + } + } + + //获取目标View的位置坐标 + int[] location = new int[2]; + anyView.getLocationOnScreen(location); + + //条件1减少不必要的回调,只关心前后发生变化的 + //条件2针对软键盘切换手写、拼音键等键盘高度发生变化 + if (isSoftInputShowing != isSoftInputShow || (isSoftInputShow && softInputHeightChanged)) { + if (listener != null) { + //第三个参数为该View需要调整的偏移量 + //此处的坐标都是相对屏幕左上角(0,0)为基准的 + listener.onChanged(isSoftInputShow, softInputHeight, location[1] + anyView.getHeight() - rect.bottom); + } + + isSoftInputShowing = isSoftInputShow; + } + } + }); + } + + + //***************STATIC METHOD****************** + + public static int getNavigationBarHeight(Context context) { + if (context == null) + return 0; + Resources resources = context.getResources(); + int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); + int height = resources.getDimensionPixelSize(resourceId); + return height; + } + + public static void showSoftInput(View view) { + if (view == null) + return; + InputMethodManager inputMethodManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputMethodManager != null) { + inputMethodManager.showSoftInput(view, 0); + } + } + + public static void hideSoftInput(View view) { + if (view == null) + return; + InputMethodManager inputMethodManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputMethodManager != null) { + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } +} diff --git a/app/src/main/java/com/example/baseframe/weight/InputView.java b/app/src/main/java/com/example/baseframe/weight/InputView.java new file mode 100644 index 0000000..7317cd6 --- /dev/null +++ b/app/src/main/java/com/example/baseframe/weight/InputView.java @@ -0,0 +1,360 @@ +package com.example.baseframe.weight; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.databinding.DataBindingUtil; + + +import com.blankj.ALog; +import com.example.baseframe.R; +import com.example.baseframe.databinding.LayoutInputHasSelectImageBinding; +import com.example.baseframe.utils.ScreenUtils; +import com.example.baseframe.weight.uploadImageList.UploadImgListBuilder; + +import java.util.ArrayList; +import java.util.List; + + +/** + * 输入文字和图片组件 + * + * 使用: + * 初始化: //传入基础视图 用于监听软键盘高度 + * parentContent = findViewById(android.R.id.content); + * inputView.setBaseView(parentContent); + * //初始化图片选择 + * inputView.setUploadImgListBuilder(new UploadImgListBuilder().setContext(this).setMaxSelectedCount(9).setNoSelectPictureDialog(true).setUploadType("order-feedback")); + * + * 监听: inputView.setInputViewListener(this); + * + * onActivityResult @Override + * protected void onActivityResult(int requestCode, int resultCode, Intent data) { + * super.onActivityResult(requestCode, resultCode, data); + * inputView.handleOnActivityResult(requestCode,resultCode,data); + * } + * + * onResume: //在Activity onResume中调用 用于恢复现场 (选择图片的时候弹窗需关闭 选择完毕后需弹出软键盘) + * @Override + * protected void onResume() { + * super.onResume(); + * inputView.onResume(); + * } + * + * 清除内容 (调用发送成功后调用): clean(); + */ + + +@SuppressLint("NonConstantResourceId") +public class InputView extends ConstraintLayout { + /** + * 上下文 + */ + private Context context; + /** + * 输入框 + */ + + EditText etInput; + /** + * 图片选择组件 + */ + + + /** + * 图片选择图标 + */ + + ImageView ivSelectPicture; + /** + * 发送按钮 + */ + + Button btSend; + /** + * 界面基础视图 (用于监听 是否弹出软键盘 ) + */ + private View baseContentView; + /** + * 回调监听 + */ + private InputViewListener inputViewListener; + + /** + * 键盘高度 + */ + private int softKeyHeight; + /** + * 是否展示 + */ + public boolean show = false; + /** + * 是否展示图片选择 + */ + private boolean showImageSelect; + /** + * 软键盘 + */ + private InputMethodManager imm; + private LayoutInputHasSelectImageBinding dataBinding; + interface BaseViewInitInf { + void initView(); + + void initData(); + + void initListener(); + } + + public InputView(@NonNull Context context) { + super(context); + init(context); + } + + public InputView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public InputView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + this.context = context; + View view = LayoutInflater.from(context).inflate(R.layout.layout_input_has_select_image, this); + dataBinding= DataBindingUtil.bind(view); + etInput = dataBinding.etInput; + + ivSelectPicture = dataBinding.ivSelectPicture; + initListener(); + } + + + + public void initListener() { + imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + etInput.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + ALog.i("监听到软键盘点击" + actionId); + if (actionId == EditorInfo.IME_ACTION_DONE) { + ALog.i("监听到软键盘点击隐藏软键盘操作 show=false"); + show = false; + } + return false; + } + }); + + } + + /** + * 设置基础视图 + * 界面基础视图 (用于监听 是否弹出软键盘 ) + */ + public void setBaseView(View baseContentView) { + this.baseContentView = baseContentView; + setBaseContentViewListener(); + } + + /** + * 是否展示图片选择 + */ + public void showImageSelect(boolean showImageSelect){ + this.showImageSelect=showImageSelect; + if(showImageSelect){ + ivSelectPicture.setVisibility(VISIBLE); + }else { + ivSelectPicture.setVisibility(GONE); + // uploadImgListView.setVisibility(GONE); + } + } + + /** + * 图片组件设置参数 + */ + public void setUploadImgListBuilder(UploadImgListBuilder uploadImgListBuilder) { + // uploadImgListView.setUploadImgListBuilder(uploadImgListBuilder); + } + + + public void onClick(View v) { + switch (v.getId()) { + case R.id.iv_select_picture: + + show = true; + ALog.i("点击图片设置 show=true"); + break; + case R.id.bt_send: + ALog.i("点击发送"); + ALog.i("设置show=false"); + show = false; + if (inputViewListener != null) { + // inputViewListener.clickSend(etInput.getText().toString(), uploadImgListView.getServiceImagesStrs()); + } + hideView(); + break; + + } + } + + /** + * 选择图片时会关闭软键盘 返回后需恢复现场 + */ + public void onResume() { + ALog.i("恢复现场"); + if (show) { + showView(); + } else { + hideView(); + } + } + + + /** + * 为基础视图添加监听 软键盘弹窗的时候显示组件 隐藏的时候隐藏组件 + */ + private void setBaseContentViewListener() { + if (baseContentView != null) { + baseContentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + Rect r = new Rect(); + ALog.i("测量界面 show=" + show); + baseContentView.getWindowVisibleDisplayFrame(r); + int displayHeight = r.bottom - r.top; + int parentHeight = baseContentView.getHeight(); + int visibleHeight = parentHeight - displayHeight; + if (visibleHeight > (ScreenUtils.getScreenHeight() / 4)) { + ALog.i("测量界面 展示软键盘了softKeyHeight= " + visibleHeight); + softKeyHeight = visibleHeight; + updateViewLocation(); + } else { + InputView.this.setVisibility(View.GONE); + } + } + }); + } + } + + /** + * 显示界面 + */ + public void showView() { + try { + ALog.i("手动显示界面"); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + InputView.this.setVisibility(View.VISIBLE); + showSoftInput(); + InputView.this.etInput.requestFocus(); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + InputView.this.etInput.requestFocus(); + } + }, 200); + } + }, 300); + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + /** + * 更新界面位置 + */ + public void updateViewLocation() { + int translationY = -softKeyHeight + ScreenUtils.getStatusBarHeight((Activity) context); + ALog.i("更新界面位置 translationY=" + translationY); + InputView.this.post(new Runnable() { + @Override + public void run() { + InputView.this.setVisibility(View.VISIBLE); + InputView.this.setTranslationY(translationY); + + } + }); + + } + + + /** + * 隐藏界面 + */ + public void hideView() { + ALog.i("隐藏界面"); + InputView.this.setVisibility(View.GONE); + hideSoftInput(); + } + + /** + * 展示软键盘 + */ + public void showSoftInput() { + //强制展示软键盘 (监听到软键盘弹起后要更新界面位置) + imm.showSoftInput(etInput, InputMethodManager.SHOW_FORCED); + } + + /** + * 隐藏软键盘 + */ + public void hideSoftInput() { + //隐藏软键盘 + imm.hideSoftInputFromWindow(etInput.getWindowToken(), 0); + } + + public void handleOnActivityResult(int requestCode, int resultCode, Intent data) { + + } + + + /** + * 设置监听 + */ + public void setInputViewListener(InputViewListener inputViewListener) { + this.inputViewListener = inputViewListener; + } + + + + /** + * 清除内容 (调用发送成功后调用) + */ + public void clean() { + etInput.setText(""); + + } + + /** + * 回调 + */ + public interface InputViewListener { + /** + * 点击发送 + */ + void clickSend(String content, List images); + + } +} diff --git a/app/src/main/java/com/example/baseframe/weight/uploadImageList/UploadImgListBuilder.java b/app/src/main/java/com/example/baseframe/weight/uploadImageList/UploadImgListBuilder.java new file mode 100644 index 0000000..169aba8 --- /dev/null +++ b/app/src/main/java/com/example/baseframe/weight/uploadImageList/UploadImgListBuilder.java @@ -0,0 +1,65 @@ +package com.example.baseframe.weight.uploadImageList; + +import android.app.Activity; + +import androidx.appcompat.app.AppCompatActivity; + +public class UploadImgListBuilder { + private AppCompatActivity mContext; + private String mType; + private int mMaxCount=9; + private boolean canSelectVideo; + /** + * 不展示底部选择弹窗 + */ + private boolean noSelectPictureDialog; + + + public UploadImgListBuilder setContext(AppCompatActivity mContext) { + this.mContext = mContext; + return this; + } + + public UploadImgListBuilder setUploadType(String mType) { + this.mType = mType; + return this; + } + + public UploadImgListBuilder setMaxSelectedCount(int mMaxCount) { + this.mMaxCount = mMaxCount; + return this; + } + + public boolean isCanSelectVideo() { + return canSelectVideo; + } + + public void setCanSelectVideo(boolean canSelectVideo) { + this.canSelectVideo = canSelectVideo; + } + + public UploadImgListBuilder get() { + return this; + } + + public AppCompatActivity getContext() { + return mContext; + } + + public String getType() { + return mType; + } + + public int getMaxCount() { + return mMaxCount; + } + + public boolean isNoSelectPictureDialog() { + return noSelectPictureDialog; + } + + public UploadImgListBuilder setNoSelectPictureDialog(boolean noSelectPictureDialog) { + this.noSelectPictureDialog = noSelectPictureDialog; + return this; + } +} diff --git a/app/src/main/java/com/example/baseframe/weight/uploadImageList/UploadImgListContract.java b/app/src/main/java/com/example/baseframe/weight/uploadImageList/UploadImgListContract.java new file mode 100644 index 0000000..81cca61 --- /dev/null +++ b/app/src/main/java/com/example/baseframe/weight/uploadImageList/UploadImgListContract.java @@ -0,0 +1,12 @@ +package com.example.baseframe.weight.uploadImageList; + +import java.util.List; + +public interface UploadImgListContract { + + void setData(List afterSaleImage); + + void clear(); + + void setUploadImgListBuilder(UploadImgListBuilder uploadImgListBuilder); +} diff --git a/app/src/main/res/layout/layout_input_has_select_image.xml b/app/src/main/res/layout/layout_input_has_select_image.xml new file mode 100644 index 0000000..f7b0eec --- /dev/null +++ b/app/src/main/res/layout/layout_input_has_select_image.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + +