Categories
Uncategorized

FIG Android 2.9 and explain the mechanism of the bubble in the chat application

2.9 Introduction Figure

Android To use a picture as a background with different amounts of text, you can design a designated area stretching image formats “.9.png”, this is the image format Figure 4.9.

Note: This image format can only be used for Android development. In ios development, you can be specified in the code of a point in stretching, but not in Android, so Android, want to achieve this effect, only 4.9 Figure (hereinafter will face pops fight can actually be , but so few people use, compatibility do not know how, click on the jump)

Figure 2.9 essence

Essence 2.9 figure is actually around each image pixel 1px increased, and use black (# FF000000) lines are marked, the other with the original without any distinction. Can refer to the following picture:

Marker location

meaning

Left – black spots

Longitudinal stretching region

On – black spots

Transverse stretching zone

Right – black line

Vertical display area

Under – black line

Lateral display area


Figure 2.9 application in Android’s

Figure 2.9 in Android Application There are three main ways

    Directly on the res directory drawable or mipmap directory

    Placed assert directory

    Downloading from the Web

The first way is the most commonly used, directly or call setBackgroundResource setImageResource method, so images can be done automatically and stretch.

As for the second or third way, if we go directly to the load .9.png, you will find a picture or picture background simply can not stretch. Nani, this is why. Hereinafter, Listen monk slowly come.

Android is not directly FIG 100.9, at compile time but convert it to another format, this format is to save it to black pixels around the Bitmap class named mNinePatchChunk of a byte [] and this is one pixel wide around the applicator removed; then in use, if this mNinePatchChunk Bitmap is not empty, and is 9patch chunk, the construction is formed NinePatchDrawable, otherwise it will be configured to BitmapDrawable, to set the final view.

Therefore, in Android, if we want to use dynamic network map download 4.9, generally need to go through the following steps:

    Use aapt tool in Figure 4.9 sdk directory will be converted to png picture

    Resolution picture when determining whether it contains NinePatchChunk, any, into NinePatchDrawable

public static void setNineImagePatch(View view, File file, String url) {
    if (file.exists()) {
        Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
        byte[] chunk = bitmap.getNinePatchChunk();
        if (NinePatch.isNinePatchChunk(chunk)) {
            NinePatchDrawable patchy = new NinePatchDrawable(view.getResources(), bitmap, chunk, new Rect(), null);
            view.setBackground(patchy);
        }
        
    }
}

Figure 2.9 upload server process


aapt conversion command

Single image file conversion

./aapt s -i xxx.9.png -o xxx.png

Batch conversion

# 批量转换
./aapt c -S inputDir -C outputDir
# inputDir 为原始.9图文件夹,outputDir 为输出文件夹

Examples of successful execution

jundeMacBook-Pro:一期气泡 junxu$ ./aapt c -S /Users/junxu/Desktop/一期气泡/气泡需求整理 -C /Users/junxu/Desktop/一期气泡/output 
Crunching PNG Files in source dir: /Users/junxu/Desktop/一期气泡/气泡需求整理
To destination dir: /Users/junxu/Desktop/一期气泡/output

note:

If it were a standard 4.9 figure, in the conversion process will complain, this time please re-designed to provide a new 4.9 map


Among the practical problems encountered in the development

Small screen phone adapter problem

At first, we cut plan is in accordance with Figure 2 times the cut, so that on a small screen phone handset bubble height is too large.

Cause Analysis:

The essence of the phenomenon is taller than the height of a single line text message picture of Figure 4.9.

Solution a (temporarily not desirable):

    I try to compress Figure 4.9, but in the end part of the phone above the display and then confusion, do not know is not a compression method Figure 4.9 wrong.

Solution two

For high-resolution and low-resolution mobile phone are issued different pictures url, we tried a nice program if density <2 when="" using="" double="" the="" chart="" picture,="" density=""> = 2 using twice the chart picture.

Third solution

Some people may have some questions it, why should adopt doubled chart, map solutions to double it? UI designer directly to a map, chart picture height 4.9 moderate is not solved. Yes, ah, we think so too, but they said for some decoration Figure 4.9, if the reduced height, some of them not very good decorative pattern cut. For example, the following picture stars.

summary

After all, Option II, Option III are in fact a compromise solution if they are directly able to do 4.9 Figure scaling, it is the perfect solution. The Android drawable in the res directory or mipmap of Figure 4.9 can really do it, to see the relevant code, it is also not found any good solution if you have a good solution, welcome message exchange.

2.9 of padding in the chart above, part of the phone failure

This is part of the Android phone bug, solutions see: https: //stackoverflow.com/questions/11065996/ninepatchdrawable-does-not-get-padding-from-chunk

public class NinePatchChunk {

    private static final String TAG = "NinePatchChunk";

    public final Rect mPaddings = new Rect();

    public int mDivX[];
    public int mDivY[];
    public int mColor[];

    private static float density = IMO.getInstance().getResources().getDisplayMetrics().density;

    private static void readIntArray(final int[] data, final ByteBuffer buffer) {
        for (int i = 0, n = data.length; i < n; ++i)
            data[i] = buffer.getInt();
    }

    private static void checkDivCount(final int length) {
        if (length == 0 || (length & 0x01) != 0)
            throw new IllegalStateException("invalid nine-patch: " + length);
    }

    public static Rect getPaddingRect(final byte[] data) {
        NinePatchChunk deserialize = deserialize(data);
        if (deserialize == null) {
            return new Rect();
        }
    }

    public static NinePatchChunk deserialize(final byte[] data) {
        final ByteBuffer byteBuffer =
                ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());

        if (byteBuffer.get() == 0) {
            return null; // is not serialized
        }

        final NinePatchChunk chunk = new NinePatchChunk();
        chunk.mDivX = new int[byteBuffer.get()];
        chunk.mDivY = new int[byteBuffer.get()];
        chunk.mColor = new int[byteBuffer.get()];

        try {
            checkDivCount(chunk.mDivX.length);
            checkDivCount(chunk.mDivY.length);
        } catch (Exception e) {
            return null;
        }


        // skip 8 bytes
        byteBuffer.getInt();
        byteBuffer.getInt();


        chunk.mPaddings.left = byteBuffer.getInt();
        chunk.mPaddings.right = byteBuffer.getInt();
        chunk.mPaddings.top = byteBuffer.getInt();
        chunk.mPaddings.bottom = byteBuffer.getInt();


        // skip 4 bytes
        byteBuffer.getInt();

        readIntArray(chunk.mDivX, byteBuffer);
        readIntArray(chunk.mDivY, byteBuffer);
        readIntArray(chunk.mColor, byteBuffer);

        return chunk;
    }
}

NinePatchDrawable patchy = new NinePatchDrawable(view.getResources(), bitmap, chunk, NinePatchChunk.getPaddingRect(chunk), null);
view.setBackground(patchy);

2.9 Download Dynamic FIG chat bubbles cause flicker

    Here we take a pre-program download (pre-download 10)

    Chat bubbles using memory cache, disk cache, ensure rapid slide RecyclerView time will not blink


Figure 2.9 understanding

The following reference Tencent Android music dynamic layout entry and decryption NinePatchChunk

Recalling the constructor NinePatchDrawable third parameter bitmap.getNinePatchChunk (), the authors suspect, aapt command is in fact bitmap images, add information NinePatchChunk, then as long as we are not able to construct this thing ourselves, we can make any picture According to the way we want to try for it?

But check the pile of official documents, it seems can not find the appropriate way to get the byte [] type of chunk parameters.

Since we can not know how to generate this chunk, then the reverse can not come to this NinePatchChunk generating method to resolve it from the perspective?

Here it is necessary to start from the source.

NinePatchChunk.java

public static NinePatchChunk deserialize(byte[] data) {
    ByteBuffer byteBuffer =
            ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
    byte wasSerialized = byteBuffer.get();
    if (wasSerialized == 0) return null;
    NinePatchChunk chunk = new NinePatchChunk();
    chunk.mDivX = new int[byteBuffer.get()];
    chunk.mDivY = new int[byteBuffer.get()];
    chunk.mColor = new int[byteBuffer.get()];
    checkDivCount(chunk.mDivX.length);
    checkDivCount(chunk.mDivY.length);
    // skip 8 bytes
    byteBuffer.getInt();
    byteBuffer.getInt();
    chunk.mPaddings.left = byteBuffer.getInt();
    chunk.mPaddings.right = byteBuffer.getInt();
    chunk.mPaddings.top = byteBuffer.getInt();
    chunk.mPaddings.bottom = byteBuffer.getInt();
    // skip 4 bytes
    byteBuffer.getInt();
    readIntArray(chunk.mDivX, byteBuffer);
    readIntArray(chunk.mDivY, byteBuffer);
    readIntArray(chunk.mColor, byteBuffer);
    return chunk;
}

In fact, this part of the analytical byte [] chunk of source code, we have been able to launch anti-rough structure of. As shown below,

According to the figure above conjecture and understanding of .9.png, intuitive feel, mDivX, mDivY, mColor three arrays are the most critical, but specifically what, we must continue to look at the source code.

ResourceTypes.h

/**
 * This chunk specifies how to split an image into segments for
 * scaling.
 *
 * There are J horizontal and K vertical segments.  These segments divide
 * the image into J*K regions as follows (where J=4 and K=3):
 *
 *      F0   S0    F1     S1
 *   +-----+----+------+-------+
 * S2|  0  |  1 |  2   |   3   |
 *   +-----+----+------+-------+
 *   |     |    |      |       |
 *   |     |    |      |       |
 * F2|  4  |  5 |  6   |   7   |
 *   |     |    |      |       |
 *   |     |    |      |       |
 *   +-----+----+------+-------+
 * S3|  8  |  9 |  10  |   11  |
 *   +-----+----+------+-------+
 *
 * Each horizontal and vertical segment is considered to by either
 * stretchable (marked by the Sx labels) or fixed (marked by the Fy
 * labels), in the horizontal or vertical axis, respectively. In the
 * above example, the first is horizontal segment (F0) is fixed, the
 * next is stretchable and then they continue to alternate. Note that
 * the segment list for each axis can begin or end with a stretchable
 * or fixed segment.
 * /

As the source code, as annotated, the picture from the NinePatch Chunk x and y axes is divided into a plurality of regions, F represents a fixed area, S represents the area stretching. mDivX, mDivY describes the position of the start of all the S region, whereas mColor described, each Segment color, usually, assigned to the source defined NO_COLOR = 0x00000001 on the line. Take for example the source code comments, mDivX, mDivY, mColor follows:

mDivX = [ S0.start, S0.end, S1.start, S1.end];
mDivY = [ S2.start, S2.end, S3.start, S3.end];
mColor = [c[0],c[1],...,c[11]]

Number of regions for the array mColor length equal division, is used to describe the color of each region, if we merely describes the stretching mode of a bitmap, then the color is not required, i.e., the source code NO_COLOR = 0x00000001

Having said that, we still through a simple example to illustrate how to construct a center point by stretching NinePatchDrawable it,

Bitmap bitmap = BitmapFactory.decodeFile(filepath);
int[] xRegions = new int[]{bitmap.getWidth() / 2, bitmap.getWidth() / 2 + 1};
int[] yRegions = new int[]{bitmap.getWidth() / 2, bitmap.getWidth() / 2 + 1};
int NO_COLOR = 0x00000001;
int colorSize = 9;
int bufferSize = xRegions.length * 4 + yRegions.length * 4 + colorSize * 4 + 32;

ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize).order(ByteOrder.nativeOrder());
// 第一个byte,要不等于0
byteBuffer.put((byte) 1);

//mDivX length
byteBuffer.put((byte) 2);
//mDivY length
byteBuffer.put((byte) 2);
//mColors length
byteBuffer.put((byte) colorSize);

//skip
byteBuffer.putInt(0);
byteBuffer.putInt(0);

//padding 先设为0
byteBuffer.putInt(0);
byteBuffer.putInt(0);
byteBuffer.putInt(0);
byteBuffer.putInt(0);

//skip
byteBuffer.putInt(0);

// mDivX
byteBuffer.putInt(xRegions[0]);
byteBuffer.putInt(xRegions[1]);

// mDivY
byteBuffer.putInt(yRegions[0]);
byteBuffer.putInt(yRegions[1]);

// mColors
for (int i = 0; i < colorSize; i++) {
    byteBuffer.putInt(NO_COLOR);
}

return byteBuffer.array();

create-a-ninepatch-ninepatchdrawable-in-runtime

Niubi also find classes in stackoverflow above, you can dynamically create 4.9 diagrams and drawing pictures, pops her face, in the beginning when it comes to android ios not want to specify an image as a dynamic stretching area.

public class NinePatchBuilder {
    int width, height;
    Bitmap bitmap;
    Resources resources;
    private ArrayList xRegions = new ArrayList();
    private ArrayList yRegions = new ArrayList();

    public NinePatchBuilder(Resources resources, Bitmap bitmap) {
        width = bitmap.getWidth();
        height = bitmap.getHeight();
        this.bitmap = bitmap;
        this.resources = resources;
    }

    public NinePatchBuilder(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public NinePatchBuilder addXRegion(int x, int width) {
        xRegions.add(x);
        xRegions.add(x + width);
        return this;
    }

    public NinePatchBuilder addXRegionPoints(int x1, int x2) {
        xRegions.add(x1);
        xRegions.add(x2);
        return this;
    }

    public NinePatchBuilder addXRegion(float xPercent, float widthPercent) {
        int xtmp = (int) (xPercent * this.width);
        xRegions.add(xtmp);
        xRegions.add(xtmp + (int) (widthPercent * this.width));
        return this;
    }

    public NinePatchBuilder addXRegionPoints(float x1Percent, float x2Percent) {
        xRegions.add((int) (x1Percent * this.width));
        xRegions.add((int) (x2Percent * this.width));
        return this;
    }

    public NinePatchBuilder addXCenteredRegion(int width) {
        int x = (int) ((this.width - width) / 2);
        xRegions.add(x);
        xRegions.add(x + width);
        return this;
    }

    public NinePatchBuilder addXCenteredRegion(float widthPercent) {
        int width = (int) (widthPercent * this.width);
        int x = (int) ((this.width - width) / 2);
        xRegions.add(x);
        xRegions.add(x + width);
        return this;
    }

    public NinePatchBuilder addYRegion(int y, int height) {
        yRegions.add(y);
        yRegions.add(y + height);
        return this;
    }

    public NinePatchBuilder addYRegionPoints(int y1, int y2) {
        yRegions.add(y1);
        yRegions.add(y2);
        return this;
    }

    public NinePatchBuilder addYRegion(float yPercent, float heightPercent) {
        int ytmp = (int) (yPercent * this.height);
        yRegions.add(ytmp);
        yRegions.add(ytmp + (int) (heightPercent * this.height));
        return this;
    }

    public NinePatchBuilder addYRegionPoints(float y1Percent, float y2Percent) {
        yRegions.add((int) (y1Percent * this.height));
        yRegions.add((int) (y2Percent * this.height));
        return this;
    }

    public NinePatchBuilder addYCenteredRegion(int height) {
        int y = (int) ((this.height - height) / 2);
        yRegions.add(y);
        yRegions.add(y + height);
        return this;
    }

    public NinePatchBuilder addYCenteredRegion(float heightPercent) {
        int height = (int) (heightPercent * this.height);
        int y = (int) ((this.height - height) / 2);
        yRegions.add(y);
        yRegions.add(y + height);
        return this;
    }

    public byte[] buildChunk() {
        if (xRegions.size() == 0) {
            xRegions.add(0);
            xRegions.add(width);
        }
        if (yRegions.size() == 0) {
            yRegions.add(0);
            yRegions.add(height);
        }
     
        int NO_COLOR = 1;//0x00000001;
        int COLOR_SIZE = 9;//could change, may be 2 or 6 or 15 - but has no effect on output
        int arraySize = 1 + 2 + 4 + 1 + xRegions.size() + yRegions.size() + COLOR_SIZE;
        ByteBuffer byteBuffer = ByteBuffer.allocate(arraySize * 4).order(ByteOrder.nativeOrder());
        byteBuffer.put((byte) 1);//was translated
        byteBuffer.put((byte) xRegions.size());//divisions x
        byteBuffer.put((byte) yRegions.size());//divisions y
        byteBuffer.put((byte) COLOR_SIZE);//color size

        //skip
        byteBuffer.putInt(0);
        byteBuffer.putInt(0);

        //padding -- always 0 -- left right top bottom
        byteBuffer.putInt(0);
        byteBuffer.putInt(0);
        byteBuffer.putInt(0);
        byteBuffer.putInt(0);

        //skip
        byteBuffer.putInt(0);

        for (int rx : xRegions)
            byteBuffer.putInt(rx); // regions left right left right ...
        for (int ry : yRegions)
            byteBuffer.putInt(ry);// regions top bottom top bottom ...

        for (int i = 0; i < COLOR_SIZE; i++)
            byteBuffer.putInt(NO_COLOR);

        return byteBuffer.array();
    }

    public NinePatch buildNinePatch() {
        byte[] chunk = buildChunk();
        if (bitmap != null)
            return new NinePatch(bitmap, chunk, null);
        return null;
    }

    public NinePatchDrawable build() {
        NinePatch ninePatch = buildNinePatch();
        if (ninePatch != null)
            return new NinePatchDrawable(resources, ninePatch);
        return null;
    }
}

Run the test code

mLlRoot = findViewById(R.id.ll_root);
try {
    InputStream is = getAssets().open("sea.png");
    Bitmap bitmap = BitmapFactory.decodeStream(is);
    for (int i = 0; i < 5; i++) {
        NinePatchDrawable ninePatchDrawable = NinePatchHelper.buildMulti(this, bitmap);
        TextView textView = new TextView(this);
        textView.setTextSize(25);
        textView.setPadding(20, 10, 20, 10);
        textView.setText(strArray[i]);
        textView.setGravity(Gravity.CENTER_VERTICAL);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        layoutParams.leftMargin = 20;
        layoutParams.rightMargin = 20;
        textView.setLayoutParams(layoutParams);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            textView.setBackground(ninePatchDrawable);
        }
        mLlRoot.addView(textView);
    }
} catch (IOException e) {
    e.printStackTrace();
}

We can see our picture perfect stretch


Reference article

  1. https://cloud.tencent.com/developer/article/1168755?
  2. https://mp.weixin.qq.com/s?__biz=MzI1NjEwMTM4OA==&mid=2651232105&idx=1&sn=fcc4fa956f329f839f2a04793e7dd3b9&mpshare=1&scene=21&srcid=0719Nyt7J8hsr4iYwOjVPXQE#wechat_redirect

Recommended Reading

Chain of Responsibility pattern and application in Android

Observer design pattern Vs event delegate (java)

Decorator Pattern and Its Applications

Builders mode (Builder) and its application

Second package third-party picture frame - using simple factory pattern

Android second package network loading frame

java proxy mode Detailed

Source Rxjava 2.x series - the basic framework analysis

Source Rxjava 2.x series - thread switching (on)

Source Rxjava 2.x series - thread switching (under)

Rxjava 2.x source Series - transform operator Map (a)

butterknife source code analysis

Step by step dismantling LeakCanary

java source code Series - takes you to read Reference and ReferenceQueue

Sweep swept away, I welcome the attention of the public micro-channel number stormjun94 (Xu code word), it is currently a programmer, Android developers not only to share knowledge, but also to share technical people grew up, including personal summary, experience in the workplace, interview experience and so on, you want to make less go a little detour.

Leave a Reply