经过两天的摸索,终于实现,现在贴出来,供自己学习记录及与小伙伴们交流分享
先看下效果图:
(添加按钮,用于添加图片)
话不多说,小翠上代码:
1.布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:topbar="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.mango.mobile.paper.modules.TimeLinePager.addRecord.AddWordActivity">
<com.mango.viewBySelf.TopBar
android:id="@+id/topbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
topbar:left_text_topbar="取消"
topbar:right_text_topbar="完成"
topbar:type="type_2" />
<ScrollView
android:id="@+id/scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/topbar"
android:fastScrollEnabled="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/et_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:maxLines="2"
android:ellipsize="end"
android:gravity="start"
android:hint="标题"
android:paddingBottom="5dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="5dp"
android:textColor="@android:color/black"
android:textSize="20dp" />
<com.mango.viewBySelf.MoreResourceEditText
android:id="@+id/et_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start"
android:hint="内容"
android:paddingBottom="5dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="5dp"
android:textSize="18dp" />
</LinearLayout>
</ScrollView>
<Button
android:id="@+id/btn_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:text="添加" />
</RelativeLayout>
其中Topba为自定义控件,重点是自定义的MoreResourceEditText
public class MoreResourceEditText extends EditText {
private final String TAG = "PATEditorView";
private Context context;
private List<String> mContentList;
public int paddingTop;
public int paddingBottom;
public int mHeight;
public int mLayoutHeight;
private final int MOVE_SLOP = 20; //移动距离临界
//滑动距离的最大边界
private int mOffsetHeight;
//是否到顶或者到底的标志
private boolean mBottomFlag = false;
private boolean isCanScroll = false;//标记内容是否触发了滚动
private float lastY = 0;
public static final String mBitmapTag = "☆";
private String mNewLineTag = "\n";
public MoreResourceEditText(Context context) {
this(context,null);
}
public MoreResourceEditText(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MoreResourceEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init(context);
}
private void init(Context context) {
mContentList = getContentList();
insertData();
}
/**
* 用集合的形式获取控件里的内容
*
* @return
*/
public List<String> getContentList() {
if (mContentList == null) {
mContentList = new ArrayList<>();
}
String content = getText().toString().replaceAll(mNewLineTag, "");
if (content.length() > 0 && content.contains(mBitmapTag)) {
String[] split = content.split("☆");
mContentList.clear();
for (String str : split) {
mContentList.add(str);
}
} else {
mContentList.add(content);
}
return mContentList;
}
/**
* 设置数据
*/
private void insertData() {
if (mContentList.size() > 0) {
for (String str : mContentList) {
if (str.indexOf(mBitmapTag) != -1) {//判断是否是图片地址
String path = str.replace(mBitmapTag, "");//还原地址字符串
Bitmap bitmap = getSmallBitmap(path, 480, 800);
//插入图片
insertBitmap(path, bitmap);
} else {
//插入文字
SpannableString ss = new SpannableString(str);
append(ss);
}
}
}
}
// 根据路径获得图片并压缩,返回bitmap用于显示
public Bitmap getSmallBitmap(String filePath, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
DisplayMetrics dm = context.getResources().getDisplayMetrics();
int w_screen = dm.widthPixels;
int w_width = w_screen;
int b_width = bitmap.getWidth();
int b_height = bitmap.getHeight();
int w_height = w_width * b_height / b_width;
bitmap = Bitmap.createScaledBitmap(bitmap, w_width, w_height, false);
return bitmap;
}
//计算图片的缩放值
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
/**
* 插入图片
*
* @param path
*/
public void insertBitmap(String path) {
Bitmap bitmap = getSmallBitmap(path, 480, 800);
insertBitmap(path, bitmap);
}
/**
* 插入图片
*
* @param bitmap
* @param path
* @return
*/
private void insertBitmap(String path, Bitmap bitmap) {
Editable edit_text = getEditableText();
int index = getSelectionStart(); // 获取光标所在位置
//插入换行符,使图片单独占一行
SpannableString newLine = new SpannableString("\n");
edit_text.insert(index, newLine);//插入图片前换行
// 创建一个SpannableString对象,以便插入用ImageSpan对象封装的图像
path = mBitmapTag + path + mBitmapTag;
SpannableString spannableString = new SpannableString(path);
// 根据Bitmap对象创建ImageSpan对象
ImageSpan imageSpan = new ImageSpan(context, bitmap);
// 用ImageSpan对象替换你指定的字符串
spannableString.setSpan(imageSpan, 0, path.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// 将选择的图片追加到EditText中光标所在位置
if (index < 0 || index >= edit_text.length()) {
edit_text.append(spannableString);
} else {
edit_text.insert(index, spannableString);
}
edit_text.insert(index, newLine);//插入图片后换行
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
//如果是需要拦截,则再拦截,这个方法会在onScrollChanged方法之后再调用一次
if (!mBottomFlag)
getParent().requestDisallowInterceptTouchEvent(true);
if (event.getAction() == MotionEvent.ACTION_MOVE){
if (lastY == 0){
lastY = event.getRawY();
}
//条件:手指move了一段距离,但是onScrollChanged函数未调用,说明文字无法滚动了,则将触摸处理权交还给ParentView
if (Math.abs(lastY - event.getRawY()) > MOVE_SLOP){
if (!isCanScroll){
getParent().requestDisallowInterceptTouchEvent(false);
}
}
}
return result;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获得内容面板
Layout mLayout = getLayout();
mLayoutHeight = mLayout.getHeight();
paddingTop = getTotalPaddingTop();
paddingBottom = getTotalPaddingBottom();
//获得控件的实际高度
mHeight = getHeight();
//计算滑动距离的边界(H_content - H_view = H_scroll)
mOffsetHeight = mLayoutHeight + paddingTop + paddingBottom - mHeight;
}
@Override
protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
isCanScroll = true;
if (vert == mOffsetHeight || vert == 0) {
//这里将处理权交还给父控件
getParent().requestDisallowInterceptTouchEvent(false);
mBottomFlag = true;
}
}
}
最后我们看下Activity里的逻辑:
public class AddWordActivity extends Activity{
@BindView(R.id.et_title)
EditTextCustomTF etTitle;
@BindView(R.id.et_content)
MoreResourceEditText etContent;
@BindView(R.id.btn_add)
Button btnAdd;
@BindView(R.id.scrollview)
ScrollView scrollView;
private int editTextHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_word);
getSupportActionBar().hide();
initView();
}
@Override
protected void initView() {
//宇宙超级无敌牛逼,在软键盘弹出时,把布局跟着软键盘上移
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
ButterKnife.bind(this);
etContent.setEnabled(true);
//etTitle.setFocusable(true);//可以通过键盘得到焦点
etContent.setFocusableInTouchMode(true);//可以通过触摸得到焦点
initEvent();
}
private void initEvent() {
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PermissionUtility.getRxPermission(AddWordActivity.this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE) //申请读权限
.subscribe(new Consumer<Boolean>() {
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public void accept(Boolean granted) throws Exception {
if (granted) {
takePictureFromAlum();
} else {
Helper.showToast("请开读写权限");
}
}
});
}
});
SoftKeyBoardListener.setListener(this, new SoftKeyBoardListener.OnSoftKeyBoardChangeListener() {
@Override
public void keyBoardShow(int height) {
editTextHeight = height;
RelativeLayout.LayoutParams layoutParams1 = (RelativeLayout.LayoutParams) scrollView.getLayoutParams();
layoutParams1.height = height + Helper.dip2px(50);
scrollView.setLayoutParams(layoutParams1);
Helper.showToast(height + "");
}
@Override
public void keyBoardHide(int height) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) scrollView.getLayoutParams();
layoutParams.height = Helper.getDisplayHeight(AddWordActivity.this);
scrollView.setLayoutParams(layoutParams);
}
});
etContent.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
//触摸的是EditText并且当前EditText可以滚动则将事件交给EditText处理;否则将事件交由其父类处理
if ((view.getId() == R.id.et_title && canVerticalScroll(etContent))) {
view.getParent().requestDisallowInterceptTouchEvent(true);//告诉父view,我的事件自己处理
if (event.getAction() == MotionEvent.ACTION_UP) {
view.getParent().requestDisallowInterceptTouchEvent(false);//告诉父view,你可以处理了
}
}
return false;
}
});
}
/**
* EditText竖直方向是否可以滚动
*
* @param editText 需要判断的EditText
* @return true:可以滚动 false:不可以滚动
*/
private boolean canVerticalScroll(EditText editText) {
//滚动的距离
int scrollY = editText.getScrollY();
//控件内容的总高度
int scrollRange = editText.getLayout().getHeight();
//控件内容总高度与实际显示高度的差值
int scrollDifference = scrollRange - editTextHeight;
if (scrollDifference == 0) {
return false;
}
return (scrollY > 0) || (scrollY < scrollDifference - 1);
}
/**
* 使用隐式意图打开系统相册
*/
private void takePictureFromAlum() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
ComponentName componentName = intent.resolveActivity(getPackageManager());
if (componentName != null) {
startActivityForResult(intent, 500);
}
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//获取图片路径
if (resultCode != RESULT_OK) {
return;
}
switch (requestCode) {
case 500: //相册
String path = GalleryUtil.getPath(this, data.getData());
etContent.insertBitmap(path);
break;
}
}
}
友情提示:其中,从相册获取照片,可能要根据自己的情况,自己修改下。
public class GalleryUtil {
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] { split[1] };
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = { column };
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}
还有比较重要的就是软键盘隐藏弹出的监听了,呶,就是这个家伙
public class SoftKeyBoardListener {
private View rootView;//activity的根视图
int rootViewVisibleHeight;//纪录根视图的显示高度
private OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener;
public SoftKeyBoardListener(AppCompatActivity activity) {
//获取activity的根视图
rootView = activity.getWindow().getDecorView();
//监听视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//获取当前根视图在屏幕上显示的大小
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int visibleHeight = r.height();
if (rootViewVisibleHeight == 0) {
rootViewVisibleHeight = visibleHeight;
return;
}
//根视图显示高度没有变化,可以看作软键盘显示/隐藏状态没有改变
if (rootViewVisibleHeight == visibleHeight) {
return;
}
//根视图显示高度变小超过200,可以看作软键盘显示了
if (rootViewVisibleHeight - visibleHeight > 200) {
if (onSoftKeyBoardChangeListener != null) {
onSoftKeyBoardChangeListener.keyBoardShow(rootViewVisibleHeight - visibleHeight);
}
rootViewVisibleHeight = visibleHeight;
return;
}
//根视图显示高度变大超过200,可以看作软键盘隐藏了
if (visibleHeight - rootViewVisibleHeight > 200) {
if (onSoftKeyBoardChangeListener != null) {
onSoftKeyBoardChangeListener.keyBoardHide(visibleHeight - rootViewVisibleHeight);
}
rootViewVisibleHeight = visibleHeight;
return;
}
}
});
}
private void setOnSoftKeyBoardChangeListener(OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener) {
this.onSoftKeyBoardChangeListener = onSoftKeyBoardChangeListener;
}
public interface OnSoftKeyBoardChangeListener {
void keyBoardShow(int height);
void keyBoardHide(int height);
}
public static void setListener(AppCompatActivity activity, OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener) {
SoftKeyBoardListener softKeyBoardListener = new SoftKeyBoardListener(activity);
softKeyBoardListener.setOnSoftKeyBoardChangeListener(onSoftKeyBoardChangeListener);
}
}
到此,已基本完成,此篇不敢说是百分百原创,急于实现效果,借鉴了网上撸友的方法,
比如参考借鉴:
等。
最后,欢迎小伙伴们交流指正。