在TextView的左上右下显示图片,可用
android:drawableLeft
android:drawableTop
android:drawableRight
android:drawableBottom
代码:
效果图:
直接调用textView的setText(Html.fromHtml(html))的方法,会导致图片加载不出来,形成一个个小方块,
textView.setText(Html.fromHtml(“html”,null,null));
在htmldemo的文件夹下再创建两个子文件夹:html(里面是html的拓展类)
Imageload(里面是图片加载类)
文件目录如下:
html | HtmlUtils: | 调用接口Spanned,在TextView中显示html |
---|---|---|
URLDrawable: | 返回异步回调的drawable对象 | |
URLImageGetter: | 加载获取图片标签,获得图片内容 | |
URLTagHandler: | 标签解析器:处理未知标签 | |
ZoomImageView: | 多点触控,手势图片缩放控件 | |
imageload | CompressTransformation: | 图片压缩转换 |
ImageLoad : | 图片加载url | |
ImageTransform: | 图片缩放转换 |
用一个类实现接口 Html的ImageGetter
继承于 ImageGetter,重写 getDrawable (String source)
方法。当解析到标签时就会回调getDrawable()方法,通过异步操作,读取本地/网络资源,获得Drawable
对象
URLImageGetter
//URLImageGetter:实现Html类中一个接口ImageGetter,用于检索获取HTML中的标签,给img标签获取图片内容
//URLImageGetter:实现Html类中一个接口ImageGetter,用于检索获取HTML中的标签,给img标签获取图片内容
public class URLImageGetter implements ImageGetter {
Context c;
TextView tv_image;
private List targets = new ArrayList<>();
public URLImageGetter(TextView t, Context c) {
this.tv_image = t;
this.c = c;
tv_image.setTag(targets);
}
@Override
public Drawable getDrawable(final String source) {
// 获取图片的url和标签
final URLDrawable urlDrawable = new URLDrawable();
final Target target = new Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Drawable drawable = new BitmapDrawable(bitmap);
drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
urlDrawable.setDrawable(drawable);
urlDrawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
tv_image.invalidate();
tv_image.setText(tv_image.getText());
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
//加载失败
errorDrawable.setBounds(0, 0, errorDrawable.getIntrinsicWidth(), errorDrawable.getIntrinsicHeight());
urlDrawable.setBounds(0, 0, errorDrawable.getIntrinsicWidth(), errorDrawable.getIntrinsicHeight());
urlDrawable.setDrawable(errorDrawable);
tv_image.invalidate();
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
//准备加载
placeHolderDrawable.setBounds(0, 0, placeHolderDrawable.getIntrinsicWidth(), placeHolderDrawable.getIntrinsicHeight());
urlDrawable.setBounds(0, 0, placeHolderDrawable.getIntrinsicWidth(), placeHolderDrawable.getIntrinsicHeight());
urlDrawable.setDrawable(placeHolderDrawable);
tv_image.invalidate();
}
};
targets.add(target);
ImageLoad.loadPlaceholder(c, source, target);
return urlDrawable;
}
怎么把异步回调的drawable返回,因为图片是异步加载的,所以我们要先建立一个BitmapDrawable,当没有异步加载的时候用来getDrawable的返回。可以包装一个drawable,继承于html.drawable,当返回正确drawable的时候填充进去,刷新一下就可以显示内容
URLDrawable
public class URLDrawable extends BitmapDrawable {
private Drawable drawable;
@Override
public void draw(Canvas canvas) {
if (drawable != null) {
drawable.draw(canvas);
}
}
public void setDrawable(Drawable drawable) {
this.drawable = drawable;
}
加载出来效果:
接着在编写一个类继承于 TagHandler,重写
handleTag()方法。支持img标签的点击处理,能够监听到点击事件。
URLTagHandler:
//标签解析器:处理未知标签,
public class URLTagHandler implements TagHandler {
private Context mContext;
private PopupWindow popupWindow;
//需要放大的图片
private ZoomImageView tecent_chat_image;
public URLTagHandler(Context context) {
mContext = context.getApplicationContext();
View popView = LayoutInflater.from(context).inflate(R.layout.pub_zoom_popwindow_layout, null);
tecent_chat_image = (ZoomImageView) popView.findViewById(R.id.image_scale_image);
popView.findViewById(R.id.image_scale_rll).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (popupWindow != null && popupWindow.isShowing()) {
popupWindow.dismiss();
}
}
});
popupWindow = new PopupWindow(popView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
popupWindow.setFocusable(true);
popupWindow.setOutsideTouchable(true);// 设置允许在外点击消失
ColorDrawable dw = new ColorDrawable(0x50000000);
popupWindow.setBackgroundDrawable(dw);
}
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
// 处理标签
if (tag.toLowerCase(Locale.getDefault()).equals("img")) {
// 获取长度
int len = output.length();
// 获取图片地址
ImageSpan[] images = output.getSpans(len - 1, len, ImageSpan.class);
String imgURL = images[0].getSource();
// 使图片可点击并监听点击事件
output.setSpan(new ClickableImage(mContext, imgURL), len - 1, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private class ClickableImage extends ClickableSpan {
private String url;
private Context context;
public ClickableImage(Context context, String url) {
this.context = context;
this.url = url;
}
@Override
public void onClick(View widget) {
// 进行图片点击之后的处理
popupWindow.showAtLocation(widget, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);
final Target target = new Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
tecent_chat_image.setImageBitmap(bitmap);
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
tecent_chat_image.setImageDrawable(errorDrawable);
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
tecent_chat_image.setImageDrawable(placeHolderDrawable);
}
};
tecent_chat_image.setTag(target);
ImageLoad.loadPlaceholder(context, url, target);
}
}
从网上找的代码感觉很好用:
ZoomImageView
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;
//图片手势缩放,点击放大
public class ZoomImageView extends ImageView implements OnGlobalLayoutListener, OnScaleGestureListener, OnTouchListener {
private boolean mOnce = false;
private float mInitScale;
//初始化的缩放量
private float mMidScale;
//最大缩放量
private float mMaxScale;
//中间缩放量
private Matrix mScaleMatrix = null;
private ScaleGestureDetector mScaleGestureDetector = null;
//缩放手势监听
private int mLastPointerCount;
//触摸点发生移动时的触摸点个数
private float mLastX;
private float mLastY;
//记录移动之前按下去的那个坐标点
private int mTouchSlop;
//开始移动的滑动距离
private boolean isCanDrag;
//是否可以移动
private boolean isCheckLeftAndRight;//是否需要考虑left和right出现白边
private boolean isCheckTopAndBottom;//是否需要考虑top和boottom出现白边
private GestureDetector mGestureDetector = null;
//手势监听,如果正在缩放中就不向下执行,防止多次双击
private boolean isAutoScale;
/**
* Matrix的对图像的处理
* Translate 平移变换
* Rotate 旋转变换
* Scale 缩放变换
* Skew 错切变换
*/
public ZoomImageView(Context context) {
this(context , null);
}
public ZoomImageView(Context context, AttributeSet attrs) {
this(context, attrs , 0);
}
public ZoomImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mScaleMatrix = new Matrix();
setScaleType(ScaleType.MATRIX);//设置缩放类型
mScaleGestureDetector = new ScaleGestureDetector(context, this);
setOnTouchListener(this);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mGestureDetector = new GestureDetector(
context,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
if(isAutoScale == true) {
return true;
}
//缩放的中心点
float x = e.getX();
float y = e.getY();
//如果当前缩放值小于这个临界值 则进行放大
if(getCurrentScale() < mMidScale) {
postDelayed(new AutoScaleRunnable(mMidScale, x, y) , 16);
isAutoScale = true;
}
else { //如果当前缩放值大于这个临界值 则进行缩小操作 缩小到mInitScale
postDelayed(new AutoScaleRunnable(mInitScale, x, y) , 16);
isAutoScale = true;
}
return true;
}
});
}
/**
* ImageView
*图片初始化其大小,把图片定位到屏幕中央,并进行初始化缩放适应屏幕
*/
@Override
public void onGlobalLayout() {
if(mOnce == false) {
//获取imageview宽高
int width = getWidth();
int height = getHeight();
Drawable drawable = getDrawable();
if(drawable == null) {
return;
}
int drawableWidth = drawable.getIntrinsicWidth();
int drawableHeight = drawable.getIntrinsicHeight();
float scale = 1.0f;
// 如果图片的宽或者高大于屏幕,则缩放至屏幕的宽或者高
if(drawableWidth > width && drawableHeight < height) {
scale = width * 1.0f / drawableWidth;
}
if(drawableWidth < width && drawableHeight > height) {
scale = height * 1.0f / drawableHeight;
}
// 如果宽和高都大于屏幕,则让其按按比例适应屏幕大小
if(drawableWidth > width && drawableHeight > height) {
scale = Math.min(width * 1.0f / drawableWidth , height * 1.0f / drawableHeight);
}
if(drawableWidth < width && drawableHeight < height) {
scale = Math.min(width * 1.0f / drawableWidth , height * 1.0f / drawableHeight);
}
// 图片移动至屏幕中心
mInitScale = scale;
mMidScale = mInitScale * 2;
mMaxScale = mInitScale * 4;
int dx = width / 2 - drawableWidth / 2;
int dy = height / 2 - drawableHeight / 2;
mScaleMatrix.postTranslate(dx, dy);
mScaleMatrix.postScale(mInitScale , mInitScale , width / 2 , height / 2);
setImageMatrix(mScaleMatrix);
mOnce = true;
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@SuppressWarnings("deprecation")
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
/**
* 获取当前缩放比例
*/
public float getCurrentScale() {
//Matrix为一个3*3的矩阵,一共9个值,复制到这个数组当中
float[] values = new float[9];
mScaleMatrix.getValues(values);
return values[Matrix.MSCALE_X];//取出图片宽度的缩放比例
}
/**
*处理图片缩放
*/
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = getCurrentScale();
//当前相对于初始尺寸的缩放(之前matrix中获得)
float scaleFactor = detector.getScaleFactor();
//这个时刻缩放的/当前缩放尺度 (现在手势获取)
if(getDrawable() == null) {
return true;
}
//按比例放大缩小
if( (scale < mMaxScale && scaleFactor > 1.0f) || (scale > mInitScale && scaleFactor < 1.0f) ) {
if(scale * scaleFactor < mInitScale) {
scaleFactor = mInitScale / scale;
}
if(scale * scaleFactor > mMaxScale) {
scaleFactor = mMaxScale / scale;
}
}
//缩放中心是屏幕中心点
mScaleMatrix.postScale(scaleFactor , scaleFactor , detector.getFocusX() , detector.getFocusY());
checkBorderAndCenterWhenScale();
setImageMatrix(mScaleMatrix);
return true;
}
/**
* 获得图片放大缩小以后的宽和高,以及l,r,t,b
*/
private RectF getMatrixRectF() {
Matrix matrix = mScaleMatrix;
RectF rectF = new RectF();
Drawable drawable = getDrawable();
if(drawable != null) {
rectF.set(0 , 0 , drawable.getIntrinsicWidth() , drawable.getIntrinsicHeight());
matrix.mapRect(rectF);
}
return rectF;
}
/**
* 在缩放时,解决上下左右留白的情况
*/
private void checkBorderAndCenterWhenScale() {
RectF rect = getMatrixRectF();
float deltaX = 0.0f;
float deltaY = 0.0f;
int width = getWidth();
int height = getHeight();
if(rect.width() >= width) {
if(rect.left > 0) {
deltaX = -rect.left;
}
if(rect.right < width) {
deltaX = width - rect.right;
}
}
if(rect.height() >= height) {
if(rect.top > 0) {
deltaY = -rect.top;
}
if(rect.bottom < height) {
deltaY = height - rect.bottom;
}
}
if(rect.width() < width) {
deltaX = width / 2f - rect.right + rect.width() / 2f;
}
if(rect.height() < height) {
deltaY = height / 2f - rect.bottom + rect.height() / 2f;
}
mScaleMatrix.postTranslate(deltaX, deltaY);
}
//缩放开始一定要返回true该detector是否处理后继的缩放事件。返回false时,不会执行onScale()
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
//缩放结束时
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
}
/**
*处理现图片放大后移动查看
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
//双击事件进行关联
if(mGestureDetector.onTouchEvent(event)) {
return true;
}
mScaleGestureDetector.onTouchEvent(event);
//缩放的中心点
float x = 0;
float y = 0;
int pointerCount = event.getPointerCount();
//可能出现多手指触摸的情况 ACTION_DOWN事件只能执行一次所以多点触控不能在down事件里面处理
for(int i = 0; i < pointerCount ; i++) {
x += event.getX(i);
y += event.getY(i);
}
//取平均值,得到的就是多点触控后产生的那个点的坐标
x /= pointerCount;
y /= pointerCount;
if(mLastPointerCount != pointerCount) {
isCanDrag = false;
mLastX = x;
mLastY = y;
}
mLastPointerCount = pointerCount;
RectF rectF = getMatrixRectF();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(rectF.width() > getWidth() + 0.01 || rectF.height() > getHeight() + 0.01) {
if(getParent() instanceof ViewPager) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
break;
case MotionEvent.ACTION_MOVE:
if(rectF.width() > getWidth() + 0.01 || rectF.height() > getHeight() + 0.01) {
if(getParent() instanceof ViewPager) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
float deltaX = x - mLastX;
float deltaY = y - mLastY;
if(isCanDrag == false) {
isCanDrag = isMoveAction(deltaX , deltaY);
}
if(isCanDrag == true) {
if(getDrawable() != null) {
isCheckLeftAndRight = true;
isCheckTopAndBottom = true;
if(rectF.width() < getWidth()) {
isCheckLeftAndRight = false;
deltaX = 0;
}
if(rectF.height() < getHeight()) {
isCheckTopAndBottom = false;
deltaY = 0;
}
mScaleMatrix.postTranslate(deltaX, deltaY);
checkBorderWhenTranslate();
setImageMatrix(mScaleMatrix);
}
}
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
mLastPointerCount = 0;
break;
case MotionEvent.ACTION_CANCEL:
mLastPointerCount = 0;
break;
}
return true;
}
/**
* 放大移动的过程中解决上下左右留白的情况
*/
private void checkBorderWhenTranslate() {
RectF rectF = getMatrixRectF();
float deltaX = 0;
float deltaY = 0;
//可以上下拖动且距离屏幕上方留白 根据Android系统坐标系往上移动的值要取负值
int width = getWidth();
int height = getHeight();
if(rectF.top > 0 && isCheckTopAndBottom) {
deltaY = -rectF.top;
}
if(rectF.bottom < height && isCheckTopAndBottom) {
deltaY = height - rectF.bottom;
}
if(rectF.left > 0 && isCheckLeftAndRight) {
deltaX = -rectF.left;
}
if(rectF.right < width && isCheckLeftAndRight) {
deltaX = width - rectF.right;
}
mScaleMatrix.postTranslate(deltaX, deltaY);
}
/**
*判断是否可以拖动
*/
private boolean isMoveAction(float deltaX, float deltaY) {
return Math.sqrt(deltaX * deltaX + deltaY * deltaY) > mTouchSlop;
}
/**
* View.postDelay()方法延时执行双击放大缩小 在主线程中运行 没隔16ms给用户产生过渡的效果的
*/
private class AutoScaleRunnable implements Runnable {
//缩放目标值
private float mTargetScale;
//缩放中心点
private float x;
private float y;
private final float BIGGER = 1.07f;
private final float SMALL = 0.93f;
//可能是BIGGER可能是SMALLER
private float tmpScale;
//构造传入缩放目标值,缩放的中心点
public AutoScaleRunnable(float mTargetScale, float x, float y) {
this.mTargetScale = mTargetScale;
this.x = x;
this.y = y;
if(getCurrentScale() < mTargetScale) {
tmpScale = BIGGER; //双击放大
}
else if(getCurrentScale() > mTargetScale) {
tmpScale = SMALL; ///双击缩小
}
}
@Override
public void run() {
//执行缩放
mScaleMatrix.postScale(tmpScale , tmpScale , x , y);
checkBorderAndCenterWhenScale();
setImageMatrix(mScaleMatrix);
float currentScale = getCurrentScale();
//如果当前正在放大操作并且当前的放大尺度小于缩放的目标值,或者正在缩小并且缩小的尺度大于目标值
//则再次延时16ms递归调用直到缩放到目标值
if( (tmpScale > 1.0f && currentScale < mTargetScale) || (tmpScale < 1.0f && currentScale > mTargetScale) ) {
postDelayed(this , 16); //??this????????
}
else {
//这里我们mTrgetScale / currentScale 用目标缩放尺寸除以当前的缩放尺寸
//得到缩放比,重新执行缩放到
//mMidScale或者mInitScale
float scale = mTargetScale / currentScale;
mScaleMatrix.postScale(scale, scale, x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(mScaleMatrix);
isAutoScale = false;
}
}
}
}
其中transform(new ImageTransform())
是图片变换的,可以自定义实现图片展示的样子,这里我加了一个图片比例缩放的变换,和屏幕宽度匹配
CompressTransformation
public class CompressTransformation implements Transformation {
public CompressTransformation() {
}
@Override
public Bitmap transform(Bitmap source) {
return WeChatBitmapToByteArray(source);
}
private Bitmap WeChatBitmapToByteArray(Bitmap source) {
// 首先进行一次大范围的压缩
ByteArrayOutputStream output = new ByteArrayOutputStream();
source.compress(Bitmap.CompressFormat.JPEG, 100, output);
float zoom = (float) Math.sqrt(100 * 1024 / (float) output.toByteArray().length); //获取缩放比例
// 设置矩阵数据
Matrix matrix = new Matrix();
matrix.setScale(zoom, zoom);
// 根据矩阵数据进行新bitmap的创建
Bitmap resultBitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
output.reset();
resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, output);
if (source != resultBitmap) {
source.recycle();
}
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
return resultBitmap;
}
@Override
public String key() {
return "CompressTransformation";
}
ImageLoad
public class ImageLoad {
public static void loadPlaceholder(Context context, String url, Target target) {
Picasso picasso = new Picasso.Builder(context).loggingEnabled(true).build();
picasso.load(url)
.placeholder(R.drawable.moren)
.error(R.drawable.moren)
.transform(new ImageTransform())
.into(target);
}
}
ImageTransform
public class ImageTransform implements Transformation {
//图片处理
private String Key = "ImageTransform";
@Override
public Bitmap transform(Bitmap source) {
int targetWidth = MyApplication.applicationContext.getResources().getDisplayMetrics().widthPixels;
if (source.getWidth() == 0) {
return source;
}
double aspectRatio = (double) source.getHeight() / (double) source.getWidth();
int targetHeight = (int) (targetWidth * aspectRatio);
if (targetHeight != 0 && targetWidth != 0) {
Bitmap result = Bitmap.createScaledBitmap(source, targetWidth, targetHeight, false);
if (result != source) {
source.recycle();
}
return result;
} else {
return source;
}
}
@Override
public String key() {
return Key;
}
调用textview.setText函数实现图片加载
MainActivity.java:
ublic class MainActivity extends AppCompatActivity {
private static String HTML = "\r\n\t这是我的第一次使用html实现图片加载\r\n
\r\n\r\n\t" +
"\n"
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n
";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.textview);
//强制转化,新建的ImageGetter 实例化传入
textView.setText(HtmlUtils.getHtml(getApplicationContext(),textView,HTML));
}
}
点击放大效果:
参考博客:Android多点触控之ZoomImageView完全解析