当我们需要自己绘制点9图时,比如游戏里面的某些ui元素,重写的view绘制需要canvas绘制点等情况时。
Android的.9图原理:(讲最特殊情况,即四个角不可拉伸,中间拉伸)。
1、使用边上一像素来标记一个图的可拉伸信息。
2、从中可读出四个信息,上边不可拉伸的高度,下边不可拉伸的高度,左边不可拉伸的宽度,右边不可拉伸的宽度
3、四个信息即可标识出一个九宫格,此处我就不画图了。
4、四个角为不可拉伸部分,上、下两边中间部分只能横向拉伸,左、右两边的中间部分只能竖直拉伸,中间板块为两个方向均可以拉伸。
Android读取.9图信息的代码(此代码不是本人自己所写,是本人的一个同伴查询Android源代码后写出来的,感谢一下这位小伙伴先)此代码大家可以直接拿出去用。
import java.util.ArrayList;
import java.util.List;
import android.graphics.Bitmap;
import android.graphics.Color;
public class NinePatchLoad {
private static final int TRANSPARENT = Color.TRANSPARENT;
private static final int BLACK = Color.BLACK;
/**
* Computes and returns the 9-patch chunks.
* @param image the image containing both the content and the control outer line.
* @return the {@link NinePatchChunk}.
*/
public static NinePatchChunk load(Bitmap image) {
ensure9Patch(image);
return createChunk(image);
}
/**
* Finds the 9-patch patches and padding from a
{@link BufferedImage} image that contains
* both the image content and the control outer lines.
*/
private static NinePatchChunk createChunk(Bitmap image) {
// the size of the actual image content
int width = image.getWidth() - 2;
int height = image.getHeight() - 2;
int[] row = null;
int[] column = null;
// extract the patch line. Make sure to start at 1 and be only as long as the image content,
// to not include the outer control line.
row = getPixels(image, 1, 0, width, 1, row);
column = getPixels(image, 0, 1, 1, height, column);
boolean[] result = new boolean[1];
Pair<List<Pair<Integer>>> left = getPatches(column, result);
boolean mVerticalStartWithPatch = result[0];
result = new boolean[1];
Pair<List<Pair<Integer>>> top = getPatches(row, result);
boolean mHorizontalStartWithPatch = result[0];
int[] horizontalPatch;
if (mHorizontalStartWithPatch) {
horizontalPatch = getAlternativeLength(top.mSecond, top.mFirst);
} else {
horizontalPatch = getAlternativeLength(top.mFirst, top.mSecond);
}
int[] verticalPatch;
if (mVerticalStartWithPatch) {
verticalPatch = getAlternativeLength(left.mSecond, left.mFirst);
} else {
verticalPatch = getAlternativeLength(left.mFirst, left.mSecond);
}
int tLeft, tMiddle, tRight, lTop, lMiddle, lBottom;
if (horizontalPatch.length == 3) {
tLeft = horizontalPatch[0];
tMiddle = horizontalPatch[1];
tRight = horizontalPatch[2];
} else {
tLeft = 1;
tRight = 1;
tMiddle = width - 2;
}
if (verticalPatch.length == 3) {
lTop = verticalPatch[0];
lMiddle = verticalPatch[1];
lBottom = verticalPatch[2];
} else {
lTop = 1;
lBottom = 1;
lMiddle = height - 2;
}
Bitmap finalBitmap = Bitmap.createBitmap(image,
1, 1, image.getWidth() - 2, image.getHeight() - 2);
image.recycle();
return new NinePatchChunk(finalBitmap,
tLeft, tMiddle, tRight, lTop, lMiddle, lBottom,
horizontalPatch, verticalPatch,
mHorizontalStartWithPatch, mVerticalStartWithPatch);
}
private static int[] getPixels(Bitmap img, int x, int y, int w, int h, int[] pixels) {
if (w == 0 || h == 0) {
return new int[0];
}
if (pixels == null) {
pixels = new int[w * h];
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("Pixels array must have a length >= w * h.");
}
img.getPixels(pixels, 0, w, x, y, w, h);
return pixels;
}
// "a" should >= "b", and take "a" first and then "b", to fill an array alternatively.
private static int[] getAlternativeLength(List<Pair<Integer>> a, List<Pair<Integer>> b) {
int [] result = new int[a.size() + b.size()];
int diff = a.size() - b.size();
if (diff < 0 || diff > 1) {
return null;
}
int i = 0;
while(i < b.size()) {
result[i*2] = a.get(i).mSecond - a.get(i).mFirst;
result[i*2+1] = b.get(i).mSecond - b.get(i).mFirst;
i++;
}
if (diff > 0) {
result[i*2] = a.get(i).mSecond - a.get(i).mFirst;
}
return result;
}
/**
* Computes a list of Patch based on a pixel line.
*
* This returns both the fixed areas, and the patches (stretchable) areas.
*
* The return value is a pair of list. The first list ({@link Pair#mFirst}) is the list
* of fixed area. The second list ({@link Pair#mSecond}) is the list of stretchable areas.
*
* Each area is defined as a Pair of (start, end) coordinate in the given line.
*
* @param pixels the pixels of the control line. The line should have the same length as the
* content (i.e. it should be stripped of the first/last control pixel which are not
* used)
* @param startWithPatch a boolean array of size 1 used to return the boolean value of whether
* a patch (stretchable area) is first or not.
* @return
*/
private static Pair<List<Pair<Integer>>> getPatches(int[] pixels, boolean[] startWithPatch) {
int lastIndex = 0;
int lastPixel = pixels[0];
boolean first = true;
List<Pair<Integer>> fixed = new ArrayList<Pair<Integer>>();
List<Pair<Integer>> patches = new ArrayList<Pair<Integer>>();
for (int i = 0; i < pixels.length; i++) {
int pixel = pixels[i];
if (pixel != lastPixel) {
if (lastPixel == BLACK) {
if (first) startWithPatch[0] = true;
patches.add(new Pair<Integer>(lastIndex, i));
} else {
fixed.add(new Pair<Integer>(lastIndex, i));
}
first = false;
lastIndex = i;
lastPixel = pixel;
}
}
if (lastPixel == BLACK) {
if (first) startWithPatch[0] = true;
patches.add(new Pair<Integer>(lastIndex, pixels.length));
} else {
fixed.add(new Pair<Integer>(lastIndex, pixels.length));
}
if (patches.size() == 0) {
patches.add(new Pair<Integer>(1, pixels.length));
startWithPatch[0] = true;
fixed.clear();
}
return new Pair<List<Pair<Integer>>>(fixed, patches);
}
private static void ensure9Patch(Bitmap image) {
int width = image.getWidth();
int height = image.getHeight();
for (int i = 0; i < width; i++) {
int pixel = image.getPixel(i, 0);
if (pixel != TRANSPARENT && pixel != BLACK) {
image.setPixel(i, 0, TRANSPARENT);
}
pixel = image.getPixel(i, height - 1);
if (pixel != TRANSPARENT && pixel != BLACK) {
image.setPixel(i, height - 1, TRANSPARENT);
}
}
for (int i = 0; i < height; i++) {
int pixel = image.getPixel(0, i);
if (pixel != TRANSPARENT && pixel != BLACK) {
image.setPixel(0, i, TRANSPARENT);
}
pixel = image.getPixel(width - 1, i);
if (pixel != TRANSPARENT && pixel != BLACK) {
image.setPixel(width - 1, i, TRANSPARENT);
}
}
}
/**
* A pair of values.
*
* @param <E>
*/
static class Pair<E> {
E mFirst;
E mSecond;
Pair(E first, E second) {
mFirst = first;
mSecond = second;
}
@Override
public String toString() {
return "Pair[" + mFirst + ", " + mSecond + "]";
}
}
保持点9图信息的Java been
import android.graphics.Bitmap;
/**
* Contains info of Nine Patch
*/
public class NinePatchChunk {
// instance variables
private Bitmap mImage;
// ��һ�������Ƿ���Ժ�������
private boolean mHorizontalStartWithPatch;
// ��һ�������Ƿ������������
private boolean mVerticalStartWithPatch;
private float w;
private float h;
private float leftW;
private float tMiddle;
private float rightW;
private float topH;
private float lMiddle;
private float bottomH;
private int[] horizontalPatch;
private int[] verticalPatch;
public NinePatchChunk(){
}
public NinePatchChunk(Bitmap image,
int tL, int tM, int tR,
int lT, int lM, int lB,
int[] hPatch, int[] vPatch,
boolean hStart, boolean vStart) {
mImage = image;
w = image.getWidth();
h = image.getHeight();
leftW = tL;
tMiddle = tM;
rightW = tR;
topH = lT;
lMiddle = lM;
bottomH = lB;
horizontalPatch = hPatch;
verticalPatch = vPatch;
mHorizontalStartWithPatch = hStart;
mVerticalStartWithPatch = vStart;
}
public Bitmap getImage() {
return mImage;
}
/**
* The horizontal division of patches
* @return
*/
public int[] getHorizontalPatch() {
return horizontalPatch;
}
/**
* The vertical division of patches
* @return
*/
public int[] getVerticalPatch() {
return verticalPatch;
}
/**
* If the first horizontal area can be stretched
* @return
*/
public boolean isStartWithPatchHorizontal() {
return mHorizontalStartWithPatch;
}
/**
* If the first vertical area can be stretched
* @return
*/
public boolean isStartWithPatchVertical() {
return mVerticalStartWithPatch;
}
public String toString() {
String str = "";
str += " " + mHorizontalStartWithPatch + "\n";
str += mVerticalStartWithPatch + " ";
for (int a : horizontalPatch) {
str += a + " ";
}
str += "\n ";
for (int a : verticalPatch) {
str += a + "\n ";
}
return str;
}
public float getW(){
return w;
}
public void setW(float w) {
this.w = w;
}
public float getH() {
return h;
}
public void setH(float h) {
this.h = h;
}
public float getLiftW() {
return leftW;
}
public void setLiftW(float leftW) {
this.leftW = leftW;
}
public float getRightW() {
return rightW;
}
public void setRightW(float rightW) {
this.rightW = rightW;
}
public float getTopH() {
return topH;
}
public void setTopH(float topH) {
this.topH = topH;
}
public float getButtonH() {
return bottomH;
}
public void setButtonH(float buttonH) {
this.bottomH = buttonH;
}
}
这里代码写的不是很好,大家见谅吧。
可以看得出来,这个JavaBeen对象中就能得到此.9图的拉伸信息,然后我们在绘制时即可用此信息进行绘制。
Android里面绘制bitmap一般有两种方式,canvas绘制和OpenGL绘制,有了点9图的拉伸信息了,使用Canvas去绘制应该不是问题很大。
大致说一下OpenGL里面的绘制吧
1、构建一个能使用OpenGL绘制的简单纹理矩形。(在OpenGL里面个人喜欢把一个矩形叫做一个ImageView,这个View里面有这个View的大小、位置、纹理坐标等信息,绘制时需要传入其对应的纹理ID,这个纹理ID是绑定了Bitmap的纹理ID)
public static void drawPortrait(int shaderIndex,FloatBuffer positionBf,FloatBuffer textureCoordBf,int textureId){
GLES20.glUseProgram(programHandle[shaderIndex][shader]);
GLES20.glUniformMatrix4fv(programHandle[shaderIndex][uMVPMatrix], 1, false,MatrixState.getFinalMatrix(), 0);
GLES20.glVertexAttribPointer(programHandle[shaderIndex][aPosition], 3,GLES20.GL_FLOAT, false, 0, positionBf);
GLES20.glVertexAttribPointer(programHandle[shaderIndex][aTextureCoord], 2,GLES20.GL_FLOAT, false, 0,textureCoordBf);
GLES20.glEnableVertexAttribArray(programHandle[shaderIndex][aPosition]);
GLES20.glEnableVertexAttribArray(programHandle[shaderIndex][aTextureCoord]);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
GLES20.glUniform1i(programHandle[shaderIndex][sTexture], 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glDisableVertexAttribArray(programHandle[shaderIndex][aPosition]);
GLES20.glDisableVertexAttribArray(programHandle[shaderIndex][aTextureCoord]);
}
programHandle数组是装了OpenGL ES 2.0里面Shader程序的各个变量的引用id,OpenGL具体怎么绘图请看其相关的文章吧。
2、根据点九图信息构建九个这样的矩形。绘制点9图需要九个这样的ImageView,然后需要注意的一点是纹理的坐标是0到1,所以需要计算好每个view对应的纹理坐标是多少,根据点九图的信息来换算。
3、根据实际要绘制的大小计算九个View的大小以及位置信息,当你要把点9图绘制成一个W*H的矩形时,需要计算好九宫格中每个矩形的大小和位置,这个位置的计算请根据Opengl里面的坐标设置来换算,这个知识点比较多,就不多讲了。
4、调用OpenGL的具体绘制方法进行绘制。
本人倒是有使用OpenGL绘制点9图的代码(自实现),但是设计到OpenGL,里面就有一大堆东西,此处就不细说了哈。