Android TextView含图文HTML点击放大后图片不显示问题求助
问题描述
我有一段包含文本和图片的HTML字符串,在TextView上渲染显示正常,示例HTML如下:
HTML字符串
<span style="font-family:;">What is the major product of the following reaction?</span><img src="https://example.com/image7.png"/>
点击TextView时,我想在白色背景上放大显示内容,目前文本放大功能正常,但图片无法显示。以下是我的实现代码:
点击事件代码
binding.tvQuestion.setOnClickListener { Intent(requireContext, FullViewImageActivity::class.java).also { it.putExtra("text", binding.tvQuestion.text.toString()) startActivity(it) } }
FullViewImageActivity代码
class FullViewImageActivity : AppCompatActivity() { lateinit var binding: ActivityFullViewImageBinding private var text: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityFullViewImageBinding.inflate(layoutInflater) setContentView(binding.root) text = intent.extras!!.getString("text") if (!TextUtils.isEmpty(text)) { binding.imageView.setImageBitmap( drawMultilineTextToBitmap( this, R.drawable.white, text ) ) } else { Toast.makeText(this, "Text is empty", Toast.LENGTH_SHORT).show() } } fun drawMultilineTextToBitmap( gContext: Context, gResId: Int, gText: String? ): Bitmap? { // prepare canvas val resources = gContext.resources val scale = resources.displayMetrics.density var bitmap = BitmapFactory.decodeResource(resources, gResId) var bitmapConfig = bitmap.config // set default bitmap config if none if (bitmapConfig == null) { bitmapConfig = Bitmap.Config.ARGB_8888 } // resource bitmaps are imutable, // so we need to convert it to mutable one bitmap = bitmap.copy(bitmapConfig, true) val canvas = Canvas(bitmap) // new antialiased Paint val paint = TextPaint(Paint.ANTI_ALIAS_FLAG) // text color - #3D3D3D paint.color = Color.rgb(61, 61, 61) // text size in pixels paint.textSize = (14 * scale).toInt().toFloat() // text shadow paint.setShadowLayer(1f, 0f, 1f, Color.WHITE) // set text width to canvas width minus 16dp padding val textWidth = canvas.width - (16 * scale).toInt() // init StaticLayout for text val textLayout = StaticLayout( gText, paint, textWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false ) // get height of multiline text val textHeight = textLayout.height // get position of text's top left corner val x = ((bitmap.width - textWidth) / 2).toFloat() val y = ((bitmap.height - textHeight) / 2).toFloat() // draw text to the Canvas center canvas.save() canvas.translate(x, y) textLayout.draw(canvas) canvas.restore() return bitmap }
布局XML代码
<LinearLayout android:id="@+id/mainLay" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/black" android:orientation="vertical"> <com.app.trigerexam.exam.ui.tvzoom.TouchImageView android:id="@+id/imageView" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitCenter" /> </LinearLayout>
TouchImageView代码
public class TouchImageView extends AppCompatImageView implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener { Matrix matrix; // We can be in one of these 3 states static final int NONE = 0; static final int DRAG = 1; static final int ZOOM = 2; int mode = NONE; // Remember some things for zooming PointF last = new PointF(); PointF start = new PointF(); float minScale = 1f; float maxScale = 3f; float[] m; int viewWidth, viewHeight; static final int CLICK = 3; float saveScale = 1f; protected float origWidth, origHeight; int oldMeasuredWidth, oldMeasuredHeight; ScaleGestureDetector mScaleDetector; Context context; public TouchImageView(Context context) { super(context); sharedConstructing(context); } public TouchImageView(Context context, AttributeSet attrs) { super(context, attrs); sharedConstructing(context); } GestureDetector mGestureDetector; private void sharedConstructing(Context context) { super.setClickable(true); this.context = context; mGestureDetector = new GestureDetector(context, this); mGestureDetector.setOnDoubleTapListener(this); mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); matrix = new Matrix(); m = new float[9]; setImageMatrix(matrix); setScaleType(ScaleType.MATRIX); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { mScaleDetector.onTouchEvent(event); mGestureDetector.onTouchEvent(event); PointF curr = new PointF(event.getX(), event.getY()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: last.set(curr); start.set(last); mode = DRAG; break; case MotionEvent.ACTION_MOVE: if (mode == DRAG) { float deltaX = curr.x - last.x; float deltaY = curr.y - last.y; float fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale); float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale); matrix.postTranslate(fixTransX, fixTransY); fixTrans(); last.set(curr.x, curr.y); } break; case MotionEvent.ACTION_UP: mode = NONE; int xDiff = (int) Math.abs(curr.x - start.x); int yDiff = (int) Math.abs(curr.y - start.y); if (xDiff < CLICK && yDiff < CLICK) performClick(); break; case MotionEvent.ACTION_POINTER_UP: mode = NONE; break; } setImageMatrix(matrix); invalidate(); return true; // indicate event was handled } }); } public void setMaxZoom(float x) { maxScale = x; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { return false; } @Override public boolean onDoubleTap(MotionEvent e) { // Double tap is detected float origScale = saveScale; float mScaleFactor; if (saveScale == maxScale) { saveScale = minScale; mScaleFactor = minScale / origScale; } else { saveScale = maxScale; mScaleFactor = maxScale / origScale; } matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2); fixTrans(); return false; } @Override public boolean onDoubleTapEvent(MotionEvent e) { return false; } @Override public boolean onDown(MotionEvent e) { return false; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } @Override public void onLongPress(MotionEvent e) { } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScaleBegin(ScaleGestureDetector detector) { mode = ZOOM; return true; } @Override public boolean onScale(ScaleGestureDetector detector) { float mScaleFactor = detector.getScaleFactor(); float origScale = saveScale; saveScale *= mScaleFactor; if (saveScale > maxScale) { saveScale = maxScale; mScaleFactor = maxScale / origScale; } else if (saveScale < minScale) { saveScale = minScale; mScaleFactor = minScale / origScale; } if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight) matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2); else matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY()); fixTrans(); return true; } } void fixTrans() { matrix.getValues(m); float transX = m[Matrix.MTRANS_X]; float transY = m[Matrix.MTRANS_Y]; float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale); float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale); if (fixTransX != 0 || fixTransY != 0) matrix.postTranslate(fixTransX, fixTransY); } float getFixTrans(float trans, float viewSize, float contentSize) { float minTrans, maxTrans; if (contentSize <= viewSize) { minTrans = 0; maxTrans = viewSize - contentSize; } else { minTrans = viewSize - contentSize; maxTrans = 0; } if (trans < minTrans) return -trans + minTrans; if (trans > maxTrans) return -trans + maxTrans; return 0; } float getFixDragTrans(float delta, float viewSize, float contentSize) { if (contentSize <= viewSize) { return 0; } return delta; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); viewWidth = MeasureSpec.getSize(widthMeasureSpec); viewHeight = MeasureSpec.getSize(heightMeasureSpec); // // Rescales image on rotation // if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight || viewWidth == 0 || viewHeight == 0) return; oldMeasuredHeight = viewHeight; oldMeasuredWidth = viewWidth; if (saveScale == 1) { // Fit to screen. float scale; Drawable drawable = getDrawable(); if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) return; int bmWidth = drawable.getIntrinsicWidth(); int bmHeight = drawable.getIntrinsicHeight(); float scaleX = (float) viewWidth / (float) bmWidth; float scaleY = (float) viewHeight / (float) bmHeight; scale = Math.min(scaleX, scaleY); matrix.setScale(scale, scale); // Center the image float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight); float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth); redundantYSpace /= (float) 2; redundantXSpace /= (float) 2; matrix.postTranslate(redundantXSpace, redundantYSpace); origWidth = viewWidth - 2 * redundantXSpace; origHeight = viewHeight - 2 * redundantYSpace; setImageMatrix(matrix); } fixTrans(); }
问题原因
- 传递的
binding.tvQuestion.text.toString()仅能获取TextView解析后的纯文本,HTML中的图片标签信息完全丢失,无法还原图片内容。 drawMultilineTextToBitmap方法仅支持绘制纯文本,没有解析HTML图片、下载并绘制图片的逻辑。
解决方案
推荐使用WebView加载原始HTML的方案,实现简单且自带缩放功能,无需手动处理图片下载和绘制:
步骤1:修改点击事件,传递原始HTML字符串
binding.tvQuestion.setOnClickListener { Intent(requireContext, FullViewImageActivity::class.java).also { // 替换为你的原始HTML字符串变量 it.putExtra("html", originalHtmlString) startActivity(it) } }
步骤2:修改FullViewImageActivity布局,替换为WebView
<LinearLayout android:id="@+id/mainLay" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/black" android:orientation="vertical"> <WebView android:id="@+id/webView" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
步骤3:修改FullViewImageActivity代码,加载HTML并配置缩放
class FullViewImageActivity : AppCompatActivity() { lateinit var binding: ActivityFullViewImageBinding private var html: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityFullViewImageBinding.inflate(layoutInflater) setContentView(binding.root) html = intent.extras!!.getString("html") if (!TextUtils.isEmpty(html)) { // 配置WebView支持缩放 binding.webView.settings.apply { setSupportZoom(true) builtInZoomControls = true displayZoomControls = false loadWithOverviewMode = true useWideViewPort = true } // 加载HTML并设置白色背景、内边距和字体大小 val formattedHtml = "<html><body style='background:white; padding:16px; font-size:14pt;'>$html</body></html>" binding.webView.loadDataWithBaseURL(null, formattedHtml, "text/html", "UTF-8", null) } else { Toast.makeText(this, "内容为空", Toast.LENGTH_SHORT).show() } } }
如果坚持使用Bitmap绘制方案,需要解析HTML中的图片URL、异步下载图片后绘制到Canvas,可使用Jsoup解析HTML,同时添加网络权限,但实现复杂度较高,不推荐。
内容的提问来源于stack exchange,提问作者Android dev
相关产品推荐
相关产品推荐

