Android custom controls Series 8: onMeasure () (2) -- use onMeasure measurement to achieve image stretching without deformation and solve the screen adaptation problem,
The previous article details the principles and functions of the onMeasure/measure method in Android custom controls. For more information, see blog: Android custom controls Series 7: onMeasure () how to measure the size of a widget in the method (1). Let's take a real look at these two methods to help us solve the screen adaptation problem of images.
Please respect the original labor results. For reprinted results, please indicate the source.
Problems with ImageView
In Android applications, image display, ImageView, slideshow, and ViewPager are indispensable. Many of them are used to display images. For example, for the carousel effect of an advertisement bar, refer to the blog: advertisement bar effect realization ---- ViewPager loads large images (LruCache) and refreshes them regularly. In many cases, we all hope that the image can be filled with the parent form in width, which is in line with people's aesthetic views, but the problem arises, that is, how to define the height ?? Let's first look at the definition of an Xml layout file for a common ImageView:
<LinearLayout xmlns: android = "http://schemas.android.com/apk/res/android" xmlns: tools = "http://schemas.android.com/tools" android: layout_width = "match_parent" android: layout_height = "match_parent" android: orientation = "vertical"> <ImageView android: layout_width = "fill_parent" android: layout_height = "wrap_content" android: src = "@ drawable/recommend_39"/> <TextView android: layout_width = "wrap_content" android: layout_height = "wrap_content" android: text = "description text ......................... "/> </LinearLayout>
For ease of viewing, I added the TextView with a description under the ImageView. At this time, the parent control is filled with the parent form, while the ImageView is: horizontally filled with the parent form, vertical Package content; text is the package content; then let's look at the display effect:
The blue box above is the range of the ImageView. This effect is generally not what we want. What if we want the image in the ImageView to fill the entire form of the ImageView? Add a property: scaleType, as shown below:
<ImageView android:layout_width="fill_parent" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@drawable/recommend_39" />
Effect
You can see that the filling is full, but the image is also deformed due to vertical stretching. What should we do?
Taking a closer look, it is not difficult to find that the vertical height of the ImageView is the package content: wrapcontent. Some people may think that they can directly give the ImageView a specific dip value here, so that this ImageView matches the image aspect ratio? This is undoubtedly possible, but it is not universal... The following is an example:
Let's first implement the process of setting the height of the ImageView to make the image display a normal proportion in this specific simulator:
The actual pixel size of the above image is: 828*314, the ratio of width to height is about 2.43, and the size of the simulator we use here is 480*800 (unit: px, that is, pixels), that is to say, the width of the pixel is 480, so we need to set the height of this ImageView to the correct ratio of 2.43.
Relationship among px, dpi, and dip on Android devices:
Another knowledge point is involved here: the connection between dip and dpi:
Resolution: indicates the total number of pixels on the device's screen. For example, in the simulator above, the screen pixel size is 480 (px) * 800 (px)
Dpi (pixel density): indicates the pixels per inch. Therefore, the dpi of two devices with the same resolution may be different. If the resolution of a mobile phone is 1080*1920, the 9.7 resolution of a tablet is also 1080*1920, so the dpi of the mobile phone is much higher than that of the tablet.
Dp/dip: The full name is Density-independent pixel. The Chinese name is "Density-independent pixel", that is, the length Unit dp that we often write in xml files. Why is it called density-independent pixels? This is actually a solution to unify the display performance of devices with different resolutions. Imagine if two mobile phones have the same screen size, such as 5-inch, the resolution of mobile phone A is 720*1280, while that of mobile phone B is 1080*1920. If we want to display an image with A resolution of 200*200, the image displayed on mobile phone A is much smaller than that displayed on mobile phone B. intuitively, the width of mobile phone A is 720, the display of 200*200 images is about to occupy nearly 1/3 of the width, while B's mobile phone shows that the width is 1080. The display of 200*200 images only occupies less than 1/5 of the width, the two mobile phones are both 5-inch in size. Therefore, the sizes of images with the same resolution are different. This difference is obviously not what we want.
So dip/dp, density-independent pixels came into being. It stipulated that dip is equal to the px value of a device with a dpi (pixel density) of 160dpi, for devices with other pixel density, the corresponding dip value is calculated based on the conversion formula. This formula is used to convert px (pixels) based on dpi (equivalent to the ratio) and dip (density-independent pixels:
px = dip * (dpi / 160)dip = px / (dpi / 160)
After the above conversion, because the dip and px conversion is proportional, and this ratio is dpi/160, dpi is calculated based on the ratio of resolution and size of each device. Therefore, the size control is set using the same dip, on devices of the same size, regardless of the device resolution, the display size is the same.
Calculation of the appropriate height of ImageView
With the above knowledge, we can convert the size of our ImageView:
First, the image width to height is 2.43, And the simulator screen width is 480/2 PX. Therefore, the calculated height of the image is. 43 = 197.53px (pixel), but in general, in the xml file, the set height unit is dip, so the above conversion formula must be used: dip = px/(dpi/160), which can be queried on the parameters of this simulator. Its dpi is 240, so the calculated height should be:
197.53/(240/160) = 131.68 (dip), which is approximately 132dp, so we set the height of the above ImageView to 132dp:
<ImageView android:layout_width="fill_parent" android:layout_height="132dp" android:scaleType="fitXY" android:src="@drawable/recommend_39" />
Let's take a look at the display effect:
This shows that the ratio is completely normal.
But is the problem solved? The answer is no. We may try another simulator to display it. This time, the Nexus7 simulator is used, with a resolution of 1200*1920, dpi of 320, and ImageView parameters unchanged. Let's take a look at the effect:
We will find that the image has been stretched. Why? Let's just calculate it again:
The width of nexus7 is pixel PX, dpi is 320, and the image proportion is 2.43. Therefore, set the height of ImageView to 1200/2. 43/(320/160) = 246.91dp, while our current height is still the previous 132dp, of course we will find it stretched.
What should I do? It's crazy !!!
Override the onMeasure method to remeasure the control height to enable adaptive image display on multiple screens.
In fact, there is a solution. The idea is to let the control (ImageView) Calculate the height based on different devices, instead of computing by ourselves. What should we do? You have to use the onMeasure method mentioned in the previous blog. If you are not familiar with the measure/onMeasure method, you can take a look at Android custom controls Series 7: onMeasure () how to measure the size of a widget (I) and Android custom controls Series 2: Custom switch button (I), and so on, the following starts:
The first thing to clarify is that the custom view cannot get the width and height of the control before calling view. measure (). Next we will write it step by step:
The idea is to first write a SmartImageView to inherit from the ImageView and add the corresponding structure:
Package com. example. imageviewdemo; import android. content. context; import android. util. attributeSet; import android. widget. imageView;/*** @ author: bitter coffee *** @ version: 1.0 *** @ date: April 14, 2015 *** @ blog: http://blog.csdn.net/cyp331203 *** @ desc: SmartImageView, automatically adjust the width and height according to the given image proportion to solve the screen adaptation problem of stretch deformation */public class SmartImageView extends ImageView {public SmartImageView (Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super (context, attrs, defStyleAttr, defStyleRes);} public SmartImageView (Context context, AttributeSet attrs, int defStyle) {super (context, attrs, defStyle );} public SmartImageView (Context context, AttributeSet attrs) {super (context, attrs);} public SmartImageView (Context context) {super (context );}}
Then, in SmartImageView, add a float member variable ratio as the Proportion Value of the image, and expose a setter method to it to facilitate the setting of the image proportion.
/** Image width and height ratio */private float ratio = 2.43f; public void setRatio (float ratio) {this. ratio = ratio ;}
Then we will rewrite the onMeausre method as follows:
@ Overrideprotected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {// The mode int widthMode = MeasureSpec in the width direction passed by the parent container. getMode (widthMeasureSpec); // The mode int heightMode = MeasureSpec in the height direction passed by the parent container. getMode (heightMeasureSpec); // The int width = MeasureSpec of the width passed by the parent container. getSize (widthMeasureSpec)-getPaddingLeft ()-getPaddingRight (); // The int height = MeasureSpec value passed by the parent container. getSize (heightMeasureSpec)-getPa DdingLeft ()-getPaddingRight (); if (widthMode = MeasureSpec. EXACTLY & heightMode! = MeasureSpec. EXACTLY & ratio! = 0.0f) {// The Judgment condition is: the width mode is Exactly, that is, filling the parent form or specifying the width; // and the height mode is not Exaclty, this parameter indicates that neither fill_parent nor specific value is set. Therefore, a specific measurement is required, and the image aspect ratio has been assigned a value. It is no longer 0.0f // indicating that the width is determined, height = (int) (width/ratio + 0.5f); heightMeasureSpec = MeasureSpec. makeMeasureSpec (height, MeasureSpec. EXACTLY);} else if (widthMode! = MeasureSpec. EXACTLY & heightMode = MeasureSpec. EXACTLY & ratio! = 0.0f) {// The Judgment condition is opposite to the preceding one. The conditions of the width direction and the height direction are interchangeable. // The height is determined and the width must be measured. (int) (height * ratio + 0.5f); widthMeasureSpec = MeasureSpec. makeMeasureSpec (width, MeasureSpec. EXACTLY);} super. onMeasure (widthMeasureSpec, heightMeasureSpec );}
Pay attention to the following points for the onMeasure method:
1. Two Parameters widthMeasureSpec and heightMeasureSpec passed by the parent container are passed through MeasureSpec. getMode () is used to obtain the mode in the parameter. It corresponds to the filling mode of the control. In the previous blog: Android custom control Series 7: onMeasure () how to measure the size of a widget (I) also mentioned
① Fill_parent or specific value in the xml layout file, or directly set the specific value of width and height in the LayoutParams of the control or LayoutParams. FILL_PARENT fills the parent container, so that the mode in the above getMode parameter is: MeasureSpect. EXACTLY indicates the exact value, because in addition to directly specifying the value, filling the parent container is also an exact value.
② Setting the wrap_content method in the xml layout file or setting the LayoutParams. WRAP_CONTENT method in the code changes getMode to MeasureSpect. AT_MOST.
2. For the height or width value transmitted by the parent container, it is not necessarily the value of the width or height that the control wants. This is because the mode is different and the value represents different meanings, therefore, we need to change the height or width value through measurement to achieve the desired effect.
If the mode is EXACTLY, the passed value refers to the actual size, or the parent container wants our control to be changed to this specific size.
However, if the mode is AT_MOST, the passed value is not a specific value. Generally, it is a maximum value, because AT_MOST represents a maximum value, therefore, this value cannot exceed the upper limit.
3. We can see that we get the height, width pattern, and value passed by the parent container, and then judge through two if-else To re-measure the value, the basis for these two judgments is:
① When the width is determined (the width is EXACTLY), when the height mode is not EXACTLY (that is, when the height is not determined), the height is re-measured according to the ratio of ratio
② When the height is determined (the height is EXACTLY), when the height mode is not EXACTLY (that is, when the height is not determined), the width is re-measured according to the ratio of ratio
4. After the measurement is completed, because we have obtained the exact value of the Desired width or height, we can use MeasureSpec. the makeMeasureSpec () method calls the exact value and the exact mode to synthesize a synthetic value in the width/height direction, and finally passes the synthesized value to super. onMeasure (widthMeasureSpec, heightMeasureSpec); set the control to the desired size.
Then we can change the previous ImageView to com. example. imageviewdemo. SmartImageView in the XML layout file.
In the code, get its object through findviewbyid, and then set the image proportion through setRatio, as shown below:
<LinearLayout xmlns: android = "http://schemas.android.com/apk/res/android" xmlns: tools = "http://schemas.android.com/tools" android: layout_width = "match_parent" android: layout_height = "match_parent" android: orientation = "vertical"> <com. example. imageviewdemo. smartImageView android: layout_width = "fill_parent" android: layout_height = "wrap_content" android: scaleType = "fitXY" android: id = "@ + id/siv" android: src = "@ drawable/recommend_39"/> <TextView android: layout_width = "wrap_content" android: layout_height = "wrap_content" android: text = "description text ......................... "/> </LinearLayout>
Package com. example. imageviewdemo; import android. app. activity; import android. OS. bundle; public class MainActivity extends Activity {@ Overrideprotected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); // get the SmartImageView object SmartImageView siv = (SmartImageView) findViewById (R. id. siv); // set the value of ratio siv. setRatio (2.43f );}}
After the above, we will find that no matter what screen, whether in the horizontal screen or vertical screen, the picture can be displayed in a correct proportion,
:
240*320/2. 7-inch nexus7 1200*1920 Device
AndroidTV (1920*1080) Horizontal Screen Display Device
At last, leave a small place to show the ratio of the image. There are multiple ways to do this. One is to specify the ratio when the image is transmitted from the server, so we can get it directly, then you can set it. Then, you can determine the proportion by measuring the width and height of BitMap.
You can set the ratio by calling the setRatio () method or using custom attributes in the XML file, if you do not know, you can read this article in the column: Android custom control series 4: Custom switch button (3)-custom attributes.
Another method for defining the View will be introduced later: the onLayout method is studied. Please wait. Thank you!
Please respect the original labor results. For reprinted results, please indicate the source.