Android的图文混合实现之TextView+html加载图片

Android的图文混合实现之TextView+html加载图片

===========================================================================================

最近在进行notepad的拓展开发,就想要能够在里面进行图片的添加删除,图文混合一起。这种应该要怎么才能实现呢?于是而我在网上搜索了大量的资料。发现实现这种有好几种方法。

TextView:文本展示控件,相信大家肯定都很熟悉了。

在textview中可以用这几种方式实现图片的显示

1.XML文件中指定属性值(activity_main.xml)


在TextView的左上右下显示图片,可用

android:drawableLeft

android:drawableTop

android:drawableRight

android:drawableBottom

代码:

<TextView

android:id=“@+id/textview_01”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:drawableTop=“@drawable/ic_thumb_up_black_24dp”

android:text=“Hello World!”

app:layout_constraintBottom_toBottomOf=“parent”

app:layout_constraintLeft_toLeftOf=“parent”

app:layout_constraintRight_toRightOf=“parent”

app:layout_constraintTop_toTopOf=“parent”

app:layout_constraintVertical_bias=“0.382” >

效果图:

1

2. 通过解析HTML来显示图片


直接调用textView的setText(Html.fromHtml(html))的方法,会导致图片加载不出来,形成一个个小方块,

textView.setText(Html.fromHtml(“html”,null,null));

在htmldemo的文件夹下再创建两个子文件夹:html(里面是html的拓展类)

Imageload(里面是图片加载类)

文件目录如下:

2

各个类的作用:

| html | HtmlUtils: | 调用接口Spanned,在TextView中显示html |

| — | — | — |

| | URLDrawable: | 返回异步回调的drawable对象 |

| | URLImageGetter: | 加载获取图片标签,获得图片内容 |

| | URLTagHandler: | 标签解析器:处理未知标签 |

| | ZoomImageView: | 多点触控,手势图片缩放控件 |

| imageload | CompressTransformation: | 图片压缩转换 |

| | ImageLoad : | 图片加载url |

| | ImageTransform: | 图片缩放转换 |

(1)加载图片:

用一个类实现接口 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;

}

(2)返回异步回调的drawable

怎么把异步回调的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;

}

加载出来效果:

3

4

5

(3)图片的点击放大

接着在编写一个类继承于 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);

}

}

(4)自定义支持手势缩放

从网上找的代码感觉很好用:

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) {

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
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) {

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-kfHGSD1f-1715483648256)]

[外链图片转存中…(img-hlLaKW4c-1715483648257)]

[外链图片转存中…(img-cK84OuHJ-1715483648258)]

[外链图片转存中…(img-LiUB6tpW-1715483648259)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值