计算位图平铺高度,以便不裁剪最后一项
Calculate Bitmap tile height so last item not cropped
问题是创建将按 X 轴拉伸的平铺背景,这样最后一个元素将不会被裁剪。
我想要的(计算每个 "dot" 图像的高度,这样最后一个图块就不会被裁剪):
我有(看到最后一个元素被裁剪):
应该有一种方法可以创建自定义 class 扩展 ImageView
或 Bitmap
并手动执行该计算。但是我真的无法获得有关如何正确执行此操作的任何信息。
bg_dots.xml:
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_dot"
android:tileMode="repeat"/>
以及它的使用方式:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_dots" />
编辑:
感谢@paulina_glab 的文章和提供的信息,我已经能够创建自定义视图来绘制重复的 tile drawable。但是计算位图的实际 width\height 以适应屏幕高度仍然存在问题。
我所做的计算涉及浮点数到整数的舍入,这会带来增量。位图需要整数作为宽度和高度,但计算表明它应该是浮点值才能完全适合屏幕。
这是我带来的:
public class DotsView extends View {
private Rect rect;
private Bitmap mBitmap;
private BitmapDrawable mDrawable;
private int screenWidth;
private int screenHeight;
private int calculatedHeight;
private int calculatedWidth;
public DotsView(Context context) {
super(context);
init();
}
public DotsView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DotsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
Resources res = getResources();
//get window specs
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
//get bitmap from resource image
mBitmap = BitmapFactory.decodeResource(res, R.drawable.ic_dot);
//define width and height of custom view after all modifications
screenWidth = size.x;
screenHeight = size.y;
//calculate proportion of height\width so the last element wont be cropped
calculatedHeight = calculateHeight();
calculatedWidth = calculateWidth();
mBitmap = Bitmap.createScaledBitmap(mBitmap, calculatedWidth, calculatedHeight, false);
//create drawable from customized bitmap
mDrawable = new BitmapDrawable(res, mBitmap);
//set it to be repeating tile
mDrawable.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
//define borders of drawable to de drawn in
rect = new Rect(0, 0, screenWidth, calculatedHeight);
mDrawable.setBounds(rect);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDrawable.draw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(screenWidth, calculatedHeight);
setMeasuredDimension(screenWidth, calculatedHeight);
}
private int calculateWidth(){
/*such calculation brings delta from conversion float to int. This delta doesn't let to draw
* exactly 25 dots. Any workarounds? */
return (int)(mBitmap.getWidth() / ((float)(mBitmap.getWidth() * 25) / screenWidth)); //draw around 25 dots
}
private int calculateHeight(){
return (int)(screenHeight * 0.07); //make it around 7 percents of screen
}
}
请看calculateWidth()
方法。
它看起来像什么(由于三角洲,仍然有裁剪点):
我不知道如何通过 bitmap
/xml
做你想做的事,但是(如你所想)我可以建议创建自定义 View class.
View.onDraw() 方法让您定义自定义绘图过程 并在
View.onSizeChanged() 您可以计算正确的 count/height 个点。
您可以使用 Drawable.draw().
绘制 单点
比你得到 一个自定义的 wiget,你可以把它放在一些布局容器中(比如 TextView
,而不是 android:background
).
我写了一个 article about drawing custom views - 那里描述的案例更复杂,但你可以看到我上面提到的每个方法的用法。例如,您可以跳过有关 Path
或属性的段落。
这是一个 official tutorial。这可能对您也有用,但它非常笼统。
如果你想在Android系统中进行自定义绘图,你应该只使用Android框架,测量和布局等事情会容易得多。
return (int)(screenHeight * 0.07); //make it around 7 percents of screen
如果您 "guess" onMeasure
中的高度,您并没有真正测量视图。你想要 25 个点,沿着视图的宽度分布,所以这就是你应该做的。
- 摆脱
WindowManager
、Display
等。Android 现在有分屏模式,有软导航栏...屏幕大小并不重要。
- 使用 Android 框架。
总是相同的过程:测量、布局、绘制。
正在测量
你在onMeasure
,所以测量。 widthMeasureSpec
包含您需要的信息——在本例中为可用宽度。用 MeasureSpec.getSize(widthMeasureSpec)
.
阅读
现在你想要沿着这个宽度有 25 个点并分别有一个高度...所以你需要做的就是除以 width / 25
然后你就会知道你的视图应该有多高。
通过告诉框架你想要多高来完成测量!
setMeasuredDimension(availableWidth, neededHeight);
布局
如果有什么改变..不管是什么原因...在这里我们得到了视图的最终大小。调整可绘制对象、缩放图像、计算边界...
尽量不要做太复杂的工作,因为这可能会被调用很多。
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// called after measuring. here we know the actual size of our view
// assuming it's a square, else do some Math.min to get a valid size
int size = bottom - top;
// set the size for the drawable, maybe scale up / down, maybe center...
mDrawable.setBounds(left, top, left + size, top + size);
}
绘图
您可以通过创建一些 Paint
并在 canvas 上创建一些 drawCircle(...)
来完全自定义绘图,或者您可以只使用一些 Drawable
。点的画法有无限可能
你想要它 25 次所以添加一个循环并确保它沿着 x 轴移动。大功告成!
由于要掌握的内容很多,这里有一个简单的例子,有一个基本的可绘制对象。我还写了一篇关于 view basics 的文章,其中包含有关测量和布局的更多信息。尝试看看它是如何工作的(我自己喜欢在 Android Studio 中使用布局预览来测试我的观点!)。一旦您了解了如何测量和布局您的视图,剩下的就是正确缩放您的位图。
public class DotsView extends View {
private static final int NUM_DOTS = 25;
private Drawable mDrawable;
public DotsView(Context context) {
super(context);
init();
}
public DotsView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DotsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mDrawable = new DotDrawable();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// measure where you're supposed to measure: in onMeasure
// get all of the width
// this will work with match_parent, as well as with a width of 32dp
int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
// NUM_DOTS == 25, or whatever, use a constant or read into attributes
int neededHeight = (int) (availableWidth / (float) NUM_DOTS);
// apply the measure dimension to the view
setMeasuredDimension(availableWidth, neededHeight);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// called after measuring. here we know the actual size of our view
// assuming it's a square, else do some Math.min to get a valid size
int size = bottom - top;
// set the size for the drawable, maybe scale up / down, maybe center...
mDrawable.setBounds(left, top, left + size, top + size);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// get the width of this view and calculate the width used per dot
float widthPerDot = getWidth() / (float) NUM_DOTS;
// draw every single dot where it belongs.
for (int i = 0; i < NUM_DOTS; i++) {
// just redraw the same drawable and move it along the x axis
mDrawable.getBounds().offsetTo((int) ((i * widthPerDot)), 0);
// finally just draw the dot
mDrawable.draw(canvas);
}
}
public class DotDrawable extends android.graphics.drawable.Drawable {
Paint mPaint;
DotDrawable() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
canvas.drawCircle(bounds.centerX(), bounds.centerY(), Math.min(bounds.width(), bounds.height()) / 2, mPaint);
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter cf) {
}
@Override
public int getOpacity() {
return 0;
}
}
}
问题是创建将按 X 轴拉伸的平铺背景,这样最后一个元素将不会被裁剪。
我想要的(计算每个 "dot" 图像的高度,这样最后一个图块就不会被裁剪):
我有(看到最后一个元素被裁剪):
应该有一种方法可以创建自定义 class 扩展 ImageView
或 Bitmap
并手动执行该计算。但是我真的无法获得有关如何正确执行此操作的任何信息。
bg_dots.xml:
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_dot"
android:tileMode="repeat"/>
以及它的使用方式:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_dots" />
编辑:
感谢@paulina_glab 的文章和提供的信息,我已经能够创建自定义视图来绘制重复的 tile drawable。但是计算位图的实际 width\height 以适应屏幕高度仍然存在问题。
我所做的计算涉及浮点数到整数的舍入,这会带来增量。位图需要整数作为宽度和高度,但计算表明它应该是浮点值才能完全适合屏幕。
这是我带来的:
public class DotsView extends View {
private Rect rect;
private Bitmap mBitmap;
private BitmapDrawable mDrawable;
private int screenWidth;
private int screenHeight;
private int calculatedHeight;
private int calculatedWidth;
public DotsView(Context context) {
super(context);
init();
}
public DotsView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DotsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
Resources res = getResources();
//get window specs
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
//get bitmap from resource image
mBitmap = BitmapFactory.decodeResource(res, R.drawable.ic_dot);
//define width and height of custom view after all modifications
screenWidth = size.x;
screenHeight = size.y;
//calculate proportion of height\width so the last element wont be cropped
calculatedHeight = calculateHeight();
calculatedWidth = calculateWidth();
mBitmap = Bitmap.createScaledBitmap(mBitmap, calculatedWidth, calculatedHeight, false);
//create drawable from customized bitmap
mDrawable = new BitmapDrawable(res, mBitmap);
//set it to be repeating tile
mDrawable.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
//define borders of drawable to de drawn in
rect = new Rect(0, 0, screenWidth, calculatedHeight);
mDrawable.setBounds(rect);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDrawable.draw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(screenWidth, calculatedHeight);
setMeasuredDimension(screenWidth, calculatedHeight);
}
private int calculateWidth(){
/*such calculation brings delta from conversion float to int. This delta doesn't let to draw
* exactly 25 dots. Any workarounds? */
return (int)(mBitmap.getWidth() / ((float)(mBitmap.getWidth() * 25) / screenWidth)); //draw around 25 dots
}
private int calculateHeight(){
return (int)(screenHeight * 0.07); //make it around 7 percents of screen
}
}
请看calculateWidth()
方法。
它看起来像什么(由于三角洲,仍然有裁剪点):
我不知道如何通过 bitmap
/xml
做你想做的事,但是(如你所想)我可以建议创建自定义 View class.
View.onDraw() 方法让您定义自定义绘图过程 并在
View.onSizeChanged() 您可以计算正确的 count/height 个点。
您可以使用 Drawable.draw().
绘制 单点比你得到 一个自定义的 wiget,你可以把它放在一些布局容器中(比如 TextView
,而不是 android:background
).
我写了一个 article about drawing custom views - 那里描述的案例更复杂,但你可以看到我上面提到的每个方法的用法。例如,您可以跳过有关 Path
或属性的段落。
这是一个 official tutorial。这可能对您也有用,但它非常笼统。
如果你想在Android系统中进行自定义绘图,你应该只使用Android框架,测量和布局等事情会容易得多。
return (int)(screenHeight * 0.07); //make it around 7 percents of screen
如果您 "guess" onMeasure
中的高度,您并没有真正测量视图。你想要 25 个点,沿着视图的宽度分布,所以这就是你应该做的。
- 摆脱
WindowManager
、Display
等。Android 现在有分屏模式,有软导航栏...屏幕大小并不重要。 - 使用 Android 框架。
总是相同的过程:测量、布局、绘制。
正在测量
你在onMeasure
,所以测量。 widthMeasureSpec
包含您需要的信息——在本例中为可用宽度。用 MeasureSpec.getSize(widthMeasureSpec)
.
现在你想要沿着这个宽度有 25 个点并分别有一个高度...所以你需要做的就是除以 width / 25
然后你就会知道你的视图应该有多高。
通过告诉框架你想要多高来完成测量!
setMeasuredDimension(availableWidth, neededHeight);
布局
如果有什么改变..不管是什么原因...在这里我们得到了视图的最终大小。调整可绘制对象、缩放图像、计算边界...
尽量不要做太复杂的工作,因为这可能会被调用很多。
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// called after measuring. here we know the actual size of our view
// assuming it's a square, else do some Math.min to get a valid size
int size = bottom - top;
// set the size for the drawable, maybe scale up / down, maybe center...
mDrawable.setBounds(left, top, left + size, top + size);
}
绘图
您可以通过创建一些 Paint
并在 canvas 上创建一些 drawCircle(...)
来完全自定义绘图,或者您可以只使用一些 Drawable
。点的画法有无限可能
你想要它 25 次所以添加一个循环并确保它沿着 x 轴移动。大功告成!
由于要掌握的内容很多,这里有一个简单的例子,有一个基本的可绘制对象。我还写了一篇关于 view basics 的文章,其中包含有关测量和布局的更多信息。尝试看看它是如何工作的(我自己喜欢在 Android Studio 中使用布局预览来测试我的观点!)。一旦您了解了如何测量和布局您的视图,剩下的就是正确缩放您的位图。
public class DotsView extends View {
private static final int NUM_DOTS = 25;
private Drawable mDrawable;
public DotsView(Context context) {
super(context);
init();
}
public DotsView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DotsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mDrawable = new DotDrawable();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// measure where you're supposed to measure: in onMeasure
// get all of the width
// this will work with match_parent, as well as with a width of 32dp
int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
// NUM_DOTS == 25, or whatever, use a constant or read into attributes
int neededHeight = (int) (availableWidth / (float) NUM_DOTS);
// apply the measure dimension to the view
setMeasuredDimension(availableWidth, neededHeight);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// called after measuring. here we know the actual size of our view
// assuming it's a square, else do some Math.min to get a valid size
int size = bottom - top;
// set the size for the drawable, maybe scale up / down, maybe center...
mDrawable.setBounds(left, top, left + size, top + size);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// get the width of this view and calculate the width used per dot
float widthPerDot = getWidth() / (float) NUM_DOTS;
// draw every single dot where it belongs.
for (int i = 0; i < NUM_DOTS; i++) {
// just redraw the same drawable and move it along the x axis
mDrawable.getBounds().offsetTo((int) ((i * widthPerDot)), 0);
// finally just draw the dot
mDrawable.draw(canvas);
}
}
public class DotDrawable extends android.graphics.drawable.Drawable {
Paint mPaint;
DotDrawable() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
canvas.drawCircle(bounds.centerX(), bounds.centerY(), Math.min(bounds.width(), bounds.height()) / 2, mPaint);
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter cf) {
}
@Override
public int getOpacity() {
return 0;
}
}
}