Android 自定义可编辑图文混排EditText

来源:互联网 发布:淘宝买steam礼物退款 编辑:程序博客网 时间:2024/06/06 01:51

来自:http://blog.csdn.net/gaoshouxiaodi/article/details/50519344


深知各位看官品性,所以先上图,然后再听我吹《我和图文混排不得不说的故事》系列之----没有故事!(为了死气沉沉的代码更有生气,我给各位看官下载的美女)

各位同道,时隔半年,我胡汉三又回来了!不是我不想来,实在是。。人在江湖,身不由己啊!总结一句话,一入码门深如海,从此节操是路人!(不发图就感觉无法表达我此刻丰富的内心)

这半年来,更证实了一句话:需求是永无止境的。在我们迈向成功的路上,需求才是最大的绊脚石。没有需求,我们就自由了!(失业了)。

如果要问我为什么这个界面这么丑,我只好说:因为小学,中学,高中的美术课都被语数外给占用了。。。

发几句牢骚,当真你就输了,哈哈。

正文:

现在对于大多数APP来说,社区模块已经习以为常了。在发表言论时,发图功能也变得非常常见(比如和我一样内心复杂的你们)。实现类似的功能,除了富文本,还有动态布局listview,这里讲的是另外一种,自定义EditText。(就是在EditText基础上改吧改吧)

一贯的宗旨:知其然必知其所以然!

Google 为我们提供了 ImageSpan 类,专门应对在文本中插入图片。该类构造方法比较多,主要针对Bitmap 和 Drawable ,也可以根据资源ID直接加载图片。本文中用到的构造函数为:

[java] view plain copy
  1. public ImageSpan(Context context, Bitmap b)  
ImageSpan 代表用来构建的样式是图片样式,那么既然有构建的样式,就会有使用样式的方法,即用什么引入样式。Google 提供了Spannable对象来引入各种样式。用法如下:

[java] view plain copy
  1. Editable edit_text = getEditableText();  
  2.         int index = getSelectionStart(); // 获取光标所在位置  
  3.         //插入换行符,使图片单独占一行  
  4.         SpannableString newLine = new SpannableString("\n");  
  5.         edit_text.insert(index, newLine);  
  6.         // 创建一个SpannableString对象,以便插入用ImageSpan对象封装的图像  
  7.         path = mBitmapTag + path + mBitmapTag;  
  8.         SpannableString spannableString = new SpannableString(path);  
  9.         // 根据Bitmap对象创建ImageSpan对象  
  10.         ImageSpan imageSpan = new ImageSpan(mContext, bitmap);  
  11.         // 用ImageSpan对象替换你指定的字符串  
  12.         spannableString.setSpan(imageSpan, 0, path.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  
  13.         // 将选择的图片追加到EditText中光标所在位置  
  14.         if (index < 0 || index >= edit_text.length()) {  
  15.             edit_text.append(spannableString);  
  16.         } else {  
  17.             edit_text.insert(index, spannableString);  
  18.         }  
好了,注释也写得很详细,核心代码就在这里。那么我宣布自定义图文混排EditText完成了。(当然不可能了)。还是有很多坑,需要带领大家一步一步淌过来的。

通常的逻辑是在空白的编辑框里逐步的输入文字,插入图片等。。这里带大家反着顺序走一遍。

首先,假设我们有一个集合,里面存储着编辑框里的内容,包括文字和图片地址。这个时候,要做的步骤是:

[java] view plain copy
  1. /** 
  2.  * 设置数据 
  3.  */  
  4. private void insertData() {  
  5.     if (mContentList.size() > 0) {  
  6.         for (String str : mContentList) {  
  7.             if (str.indexOf(mBitmapTag) != -1) {//判断是否是图片地址  
  8.                 String path = str.replace(mBitmapTag, "");//还原地址字符串  
  9.                 Bitmap bitmap = getSmallBitmap(path, 480800);  
  10.                 //插入图片  
  11.                 insertBitmap(path, bitmap);  
  12.             } else {  
  13.                 //插入文字  
  14.                 SpannableString ss = new SpannableString(str);  
  15.                 append(ss);  
  16.             }  
  17.         }  
  18.     }  
  19. }  
设置数据,先判断集合中是否有数据,如果有,就设置进来,设置的时候,判断是否包含图片的标记,如果包含,那就去获取图片,然后通过ImageSpan设置进来。插入图片的方法为:

[java] view plain copy
  1. /** 
  2.  * 插入图片 
  3.  * 
  4.  * @param bitmap 
  5.  * @param path 
  6.  * @return 
  7.  */  
  8. private SpannableString insertBitmap(String path, Bitmap bitmap) {  
  9.     Editable edit_text = getEditableText();  
  10.     int index = getSelectionStart(); // 获取光标所在位置  
  11.     //插入换行符,使图片单独占一行  
  12.     SpannableString newLine = new SpannableString("\n");  
  13.     edit_text.insert(index, newLine);//插入图片前换行  
  14.     // 创建一个SpannableString对象,以便插入用ImageSpan对象封装的图像  
  15.     path = mBitmapTag + path + mBitmapTag;  
  16.     SpannableString spannableString = new SpannableString(path);  
  17.     // 根据Bitmap对象创建ImageSpan对象  
  18.     ImageSpan imageSpan = new ImageSpan(mContext, bitmap);  
  19.     // 用ImageSpan对象替换你指定的字符串  
  20.     spannableString.setSpan(imageSpan, 0, path.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  
  21.     // 将选择的图片追加到EditText中光标所在位置  
  22.     if (index < 0 || index >= edit_text.length()) {  
  23.         edit_text.append(spannableString);  
  24.     } else {  
  25.         edit_text.insert(index, spannableString);  
  26.     }  
  27.     edit_text.insert(index, newLine);//插入图片后换行  
  28.     return spannableString;  
  29. }  
好了,将集合中的数据显示出来的步骤完成了。那么如果没有数据呢?

看代码:

[java] view plain copy
  1. /** 
  2.  * 插入图片 
  3.  * 
  4.  * @param path 
  5.  */  
  6. public void insertBitmap(String path) {  
  7.     Bitmap bitmap = getSmallBitmap(path, 480800);  
  8.     insertBitmap(path, bitmap);  
  9. }  
哈哈,就是这么简单。到了这里,大体可以使用了。为什么说是大体可以用,因为在这里使用,会发现我们一次性插入了很多图,先不考虑内存溢出的问题,因为这是图片优化的事情,除此之外,我们面临的问题是自定义的EditText滑动不顺畅,换句话说就是,由于EditText时刻获取焦点,长图片的时候,会自动对焦点在图片底部。这就引起了无法顺畅滑动,或者干脆滑动不到上面或者下面的图片位置。无法精准定位的问题。怎么办?凉拌!代码来说话:

[java] view plain copy
  1. @Override  
  2. public boolean onTouchEvent(MotionEvent event) {  
  3.   
  4.     switch (event.getAction()) {  
  5.         case MotionEvent.ACTION_DOWN:  
  6.             oldY = event.getY();  
  7.             requestFocus();  
  8.             break;  
  9.         case MotionEvent.ACTION_MOVE:  
  10.             float newY = event.getY();  
  11.             if (Math.abs(oldY - newY) > 20) {  
  12.                 clearFocus();  
  13.             }  
  14.             break;  
  15.         case MotionEvent.ACTION_UP:  
  16.             break;  
  17.         default:  
  18.             break;  
  19.     }  
  20.     return super.onTouchEvent(event);  
  21. }  

很简单,复写了onTouchEvent方法,在不同的状态时,申请是否获取焦点。。这里的设置是按下时获取焦点,移动距离超过20时,取消焦点。好了,相信大家已经都明白了,万事俱备,只欠东风。。贴出全部代码。

看我这高大上的控件名字。

[java] view plain copy
  1. package com.example.zhipeng.phototexteditordemo;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Bitmap;  
  5. import android.graphics.BitmapFactory;  
  6. import android.text.Editable;  
  7. import android.text.Spannable;  
  8. import android.text.SpannableString;  
  9. import android.text.style.ImageSpan;  
  10. import android.util.AttributeSet;  
  11. import android.util.DisplayMetrics;  
  12. import android.view.MotionEvent;  
  13. import android.widget.EditText;  
  14.   
  15. import java.util.ArrayList;  
  16. import java.util.List;  
  17.   
  18. /** 
  19.  * Created by zhipeng on 16/1/13. 
  20.  * 图文混排编辑器 
  21.  */  
  22. public class PictureAndTextEditorView extends EditText {  
  23.     private final String TAG = "PATEditorView";  
  24.     private Context mContext;  
  25.     private List<String> mContentList;  
  26.   
  27.     public static final String mBitmapTag = "☆";  
  28.     private String mNewLineTag = "\n";  
  29.   
  30.     public PictureAndTextEditorView(Context context) {  
  31.         super(context);  
  32.         init(context);  
  33.     }  
  34.   
  35.     public PictureAndTextEditorView(Context context, AttributeSet attrs) {  
  36.         super(context, attrs);  
  37.         init(context);  
  38.     }  
  39.   
  40.     public PictureAndTextEditorView(Context context, AttributeSet attrs, int defStyleAttr) {  
  41.         super(context, attrs, defStyleAttr);  
  42.         init(context);  
  43.     }  
  44.   
  45.     private void init(Context context) {  
  46.         mContext = context;  
  47.         mContentList = getmContentList();  
  48.         insertData();  
  49.     }  
  50.   
  51.     /** 
  52.      * 设置数据 
  53.      */  
  54.     private void insertData() {  
  55.         if (mContentList.size() > 0) {  
  56.             for (String str : mContentList) {  
  57.                 if (str.indexOf(mBitmapTag) != -1) {//判断是否是图片地址  
  58.                     String path = str.replace(mBitmapTag, "");//还原地址字符串  
  59.                     Bitmap bitmap = getSmallBitmap(path, 480800);  
  60.                     //插入图片  
  61.                     insertBitmap(path, bitmap);  
  62.                 } else {  
  63.                     //插入文字  
  64.                     SpannableString ss = new SpannableString(str);  
  65.                     append(ss);  
  66.                 }  
  67.             }  
  68.         }  
  69.     }  
  70.   
  71.     /** 
  72.      * 插入图片 
  73.      * 
  74.      * @param bitmap 
  75.      * @param path 
  76.      * @return 
  77.      */  
  78.     private SpannableString insertBitmap(String path, Bitmap bitmap) {  
  79.         Editable edit_text = getEditableText();  
  80.         int index = getSelectionStart(); // 获取光标所在位置  
  81.         //插入换行符,使图片单独占一行  
  82.         SpannableString newLine = new SpannableString("\n");  
  83.         edit_text.insert(index, newLine);//插入图片前换行  
  84.         // 创建一个SpannableString对象,以便插入用ImageSpan对象封装的图像  
  85.         path = mBitmapTag + path + mBitmapTag;  
  86.         SpannableString spannableString = new SpannableString(path);  
  87.         // 根据Bitmap对象创建ImageSpan对象  
  88.         ImageSpan imageSpan = new ImageSpan(mContext, bitmap);  
  89.         // 用ImageSpan对象替换你指定的字符串  
  90.         spannableString.setSpan(imageSpan, 0, path.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  
  91.         // 将选择的图片追加到EditText中光标所在位置  
  92.         if (index < 0 || index >= edit_text.length()) {  
  93.             edit_text.append(spannableString);  
  94.         } else {  
  95.             edit_text.insert(index, spannableString);  
  96.         }  
  97.         edit_text.insert(index, newLine);//插入图片后换行  
  98.         return spannableString;  
  99.     }  
  100.   
  101.   
  102.     /** 
  103.      * 插入图片 
  104.      * 
  105.      * @param path 
  106.      */  
  107.     public void insertBitmap(String path) {  
  108.         Bitmap bitmap = getSmallBitmap(path, 480800);  
  109.         insertBitmap(path, bitmap);  
  110.     }  
  111.   
  112.     /** 
  113.      * 用集合的形式获取控件里的内容 
  114.      * 
  115.      * @return 
  116.      */  
  117.     public List<String> getmContentList() {  
  118.         if (mContentList == null) {  
  119.             mContentList = new ArrayList<String>();  
  120.         }  
  121.         String content = getText().toString().replaceAll(mNewLineTag, "");  
  122.         if (content.length() > 0 && content.contains(mBitmapTag)) {  
  123.             String[] split = content.split("☆");  
  124.             mContentList.clear();  
  125.             for (String str : split) {  
  126.                 mContentList.add(str);  
  127.             }  
  128.         } else {  
  129.             mContentList.add(content);  
  130.         }  
  131.   
  132.         return mContentList;  
  133.     }  
  134.   
  135.     /** 
  136.      * 设置显示的内容集合 
  137.      * 
  138.      * @param contentList 
  139.      */  
  140.     public void setmContentList(List<String> contentList) {  
  141.         if (mContentList == null) {  
  142.             mContentList = new ArrayList<>();  
  143.         }  
  144.         this.mContentList.clear();  
  145.         this.mContentList.addAll(contentList);  
  146.         insertData();  
  147.     }  
  148.   
  149.     float oldY = 0;  
  150.   
  151.     @Override  
  152.     public boolean onTouchEvent(MotionEvent event) {  
  153.   
  154.         switch (event.getAction()) {  
  155.             case MotionEvent.ACTION_DOWN:  
  156.                 oldY = event.getY();  
  157.                 requestFocus();  
  158.                 break;  
  159.             case MotionEvent.ACTION_MOVE:  
  160.                 float newY = event.getY();  
  161.                 if (Math.abs(oldY - newY) > 20) {  
  162.                     clearFocus();  
  163.                 }  
  164.                 break;  
  165.             case MotionEvent.ACTION_UP:  
  166.                 break;  
  167.             default:  
  168.                 break;  
  169.         }  
  170.         return super.onTouchEvent(event);  
  171.     }  
  172.   
  173.     // 根据路径获得图片并压缩,返回bitmap用于显示  
  174.     public Bitmap getSmallBitmap(String filePath, int reqWidth, int reqHeight) {  
  175.         final BitmapFactory.Options options = new BitmapFactory.Options();  
  176.         options.inJustDecodeBounds = true;  
  177.         BitmapFactory.decodeFile(filePath, options);  
  178.   
  179.         // Calculate inSampleSize  
  180.         options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  181.   
  182.         // Decode bitmap with inSampleSize set  
  183.         options.inJustDecodeBounds = false;  
  184.   
  185.         Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);  
  186.         DisplayMetrics dm = mContext.getResources().getDisplayMetrics();  
  187.         int w_screen = dm.widthPixels;  
  188.         int w_width = w_screen;  
  189.         int b_width = bitmap.getWidth();  
  190.         int b_height = bitmap.getHeight();  
  191.         int w_height = w_width * b_height / b_width;  
  192.         bitmap = Bitmap.createScaledBitmap(bitmap, w_width, w_height, false);  
  193.         return bitmap;  
  194.     }  
  195.   
  196.     //计算图片的缩放值  
  197.     public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {  
  198.         final int height = options.outHeight;  
  199.         final int width = options.outWidth;  
  200.         int inSampleSize = 1;  
  201.   
  202.         if (height > reqHeight || width > reqWidth) {  
  203.             final int heightRatio = Math.round((float) height / (float) reqHeight);  
  204.             final int widthRatio = Math.round((float) width / (float) reqWidth);  
  205.             inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  
  206.         }  
  207.         return inSampleSize;  
  208.     }  
  209. }  

EditActivity

[java] view plain copy
  1. package com.example.zhipeng.phototexteditordemo;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Intent;  
  5. import android.net.Uri;  
  6. import android.os.Bundle;  
  7. import android.text.Editable;  
  8. import android.text.TextWatcher;  
  9. import android.util.Log;  
  10. import android.view.View;  
  11. import android.widget.Button;  
  12.   
  13. import java.util.ArrayList;  
  14. import java.util.List;  
  15.   
  16. /** 
  17.  * Created by zhipeng on 16/1/13. 
  18.  */  
  19. public class EditActivity extends Activity {  
  20.   
  21.     private PictureAndTextEditorView mEditText;  
  22.     private Button mButton;  
  23.   
  24.     @Override  
  25.     protected void onCreate(Bundle savedInstanceState) {  
  26.         super.onCreate(savedInstanceState);  
  27.         setContentView(R.layout.activity_edit);  
  28.   
  29.         mEditText = (PictureAndTextEditorView) findViewById(R.id.edit_text);  
  30.         mButton = (Button) findViewById(R.id.button_add_picture);  
  31.         ///storage/emulated/0/Mob/cn.xfz.app/cache/images/1450915925_4457.jpg  
  32.         ///storage/emulated/0/Pictures/1450770237621.jpg  
  33.         ///storage/emulated/0/Pictures/1450769835187.jpg  
  34.         ///storage/emulated/0/Mob/cn.xfz.app/cache/images/1450684805_82970.jpg  
  35.         List<String> list = new ArrayList<>();//这里是测试用的,对于图片地址,各位还是要自己设置一下  
  36.         list.add("你说");  
  37.         list.add(PictureAndTextEditorView.mBitmapTag+"/storage/emulated/0/Mob/cn.xfz.app/cache/images/1450915925_4457.jpg");  
  38.         list.add("我在哪");  
  39.         list.add(PictureAndTextEditorView.mBitmapTag+"/storage/emulated/0/Pictures/1450770237621.jpg");  
  40.         list.add("不告诉你");  
  41.         list.add(PictureAndTextEditorView.mBitmapTag+"/storage/emulated/0/Pictures/1450769835187.jpg");  
  42.         list.add(PictureAndTextEditorView.mBitmapTag+"/storage/emulated/0/Mob/cn.xfz.app/cache/images/1450684805_82970.jpg");  
  43.         list.add("嘿嘿");  
  44.         list.add(PictureAndTextEditorView.mBitmapTag+"/storage/emulated/0/Mob/cn.xfz.app/cache/images/1450915925_4457.jpg");  
  45.   
  46. //        mEditText.setmContentList(list);  
  47.   
  48.   
  49.         mButton.setOnClickListener(new View.OnClickListener() {  
  50.             @Override  
  51.             public void onClick(View v) {  
  52.                 PicturePickUtils.selectPicFromLocal(EditActivity.this,888);//获取手机本地图片的代码,大家可以自行实现  
  53.             }  
  54.         });  
  55.   
  56.         mEditText.addTextChangedListener(new TextWatcher() {  
  57.             @Override  
  58.             public void beforeTextChanged(CharSequence s, int start, int count, int after) {  
  59.   
  60.             }  
  61.   
  62.             @Override  
  63.             public void onTextChanged(CharSequence s, int start, int before, int count) {  
  64.                 Log.i("EditActivity",mEditText.getmContentList().toString());  
  65.             }  
  66.   
  67.             @Override  
  68.             public void afterTextChanged(Editable s) {  
  69.   
  70.             }  
  71.         });  
  72.     }  
  73.   
  74.     @Override  
  75.     protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
  76.         super.onActivityResult(requestCode, resultCode, data);  
  77.         if (resultCode == RESULT_OK) {  
  78.             switch (requestCode) {  
  79.                 case 888:  
  80.                     if (data != null) {  
  81.   
  82.                         Uri selectedImage = data.getData();  
  83.                         String imageurl = UriUtils.getImageAbsolutePath(this, selectedImage);  
  84.   
  85.                         mEditText.insertBitmap(imageurl);  
  86.   
  87.   
  88.   
  89.                     }  
  90.                 default:  
  91.                     break;  
  92.             }  
  93.         }  
  94.     }  
  95. }  

xml

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:background="#ffffff"  
  6.     android:orientation="vertical">  
  7.   
  8.   
  9.     <com.example.zhipeng.phototexteditordemo.PictureAndTextEditorView  
  10.         android:id="@+id/edit_text"  
  11.         android:layout_width="match_parent"  
  12.         android:layout_height="0dp"  
  13.         android:layout_weight="1"  
  14.         android:scrollbars="vertical"  
  15.         android:scrollbarStyle="outsideOverlay"  
  16.         android:gravity="start"  
  17.         android:hint="132456"  
  18.         />  
  19.   
  20.   
  21.     <Button  
  22.         android:id="@+id/button_add_picture"  
  23.         android:layout_width="match_parent"  
  24.         android:layout_height="50dp"  
  25.         android:text="点我插图"  
  26.         android:layout_alignParentBottom="true"/>  
  27.   
  28. </LinearLayout>  


转载请注明出处。。