I was wondering how I would go about drawing multiple bitmaps on one screen. I want to have a drawing area that I can scroll across multiple bitmaps and view the drawing that are on each bitmap. For example there would be an 2x2 tiled area that I want to scroll across. I'm having trouble figuring out how I would go about showing part of either 2 or 4 bitmaps while I would be scrolling.
EDIT: This is what it would look like
I would suggest creating your won implementation of a viewgroup using this model:
public class MyLayout extends ViewGroup{
public MyLayout(Context c, AttributeSet attr){
super(c, attr);
//Add this to be albe to draw by yourself
this.setWillNotDraw(false);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
#Override
public onDraw(Canvas c){
super.onDraw(c);
//Do all your drawing in here
//You can use canvas.drawBitmap etc...
}
}
This is a good tutorial for a custom viewgroup on Android Dev page:
http://developer.android.com/reference/android/view/ViewGroup.html
Related
How do you make a horizontally scrolling ListView for vertical Mongolian script in Android apps?
Background
Android has fairly good support for many of the world's languages, even RTL languages like Arabic and Hebrew. However, there is no built in support for top-to-bottom languages like traditional Mongolian (which is still very much alive in Inner Mongolia and not to be confused with Cyrillic Mongolian). The following graphic shows the text direction with English added for clarity.
Since this functionality is not built into Android, it makes almost every single aspect of app development extremely difficult. This is expecially true with horizontal ListViews, which are not supported out of the box in Android. There is also very, very little information available online. There are a number of app developers for traditional Mongolian, but whether it is for commercial reasons or otherwise, they do not seem to make their code open source.
Because of these difficulties I would like to make a series of StackOverflow questions that can serve as a central place to collect answers for some of the more difficult programming problems related to traditional Mongolian app development. Even if you are not literate in Mongolian, your help reviewing the code, making comments and questions, giving answers or even up-voting the question would be appreciated.
Mongolian Horizontally Scrolling ListView with Vertical Script
A Mongolian ListView needs to have the following requirements:
Scrolls horizontally from left to right
Touch events work the same as with a normal ListView
Custom layouts are supported the same as in a normal ListView
Also needs to support everything that a Mongolian TextView would support:
Supports a traditional Mongolian font
Displays text vertically from top to bottom
Line wrapping goes from left to right.
Line breaks occur at a space (same as English)
The image below shows the basic functionality a Mongolian ListView should have:
My answer is below, but I welcome other ways of solving this problem.
Other related questions in this series:
How to make a traditional Mongolian script TextView in Android
How to make a traditional Mongolian script EditText in Android
More to come... (Toast, Dialog, Menu)
iOS:
How do you make a vertical text UILabel and UITextView for iOS in Swift?
Update
RecyclerViews have a horizontal layout. So it is relatively easy to put a Vertical Mongolian TextView inside one of these. Here is an example from mongol-library.
See this answer for a general solution to using a RecyclerView to make a horizontally scrolling list.
Old answer
It is quite unfortunate that horizontal ListViews are not provided by the Android API. There are a number of StackOverflow Q&As that talk about how to do them, though. Here are a couple samples:
How can I make a horizontal ListView in Android?
Horizontal ListView in Android?
But when I actually tried to implement these suggestions as well as incorporate Mongolian vertical text, I was having a terrible time. Somewhere in my search I found a slightly different answer. It was a class that rotated an entire layout. It did so by extending ViewGroup. In this way anything (including a ListView) can be put in the ViewGroup and it gets rotated. All the touch events work, too.
As I explained in my answer about Mongolian TextViews, it is not enough to simply rotate Mongolian text. That would be enough if every ListView item (or other text element in the ViewGroup) was only a single line, but rotating multiple lines make the line wrap go the wrong direction. However, mirroring the layout horizontally and also using a vertically mirrored font can overcome this, as is shown in the following image.
I adapted the rotated ViewGroup code to also do the horizontal mirroring.
public class MongolViewGroup extends ViewGroup {
private int angle = 90;
private final Matrix rotateMatrix = new Matrix();
private final Rect viewRectRotated = new Rect();
private final RectF tempRectF1 = new RectF();
private final RectF tempRectF2 = new RectF();
private final float[] viewTouchPoint = new float[2];
private final float[] childTouchPoint = new float[2];
private boolean angleChanged = true;
public MongolViewGroup(Context context) {
this(context, null);
}
public MongolViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
}
public View getView() {
return getChildAt(0);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final View view = getView();
if (view != null) {
measureChild(view, heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(resolveSize(view.getMeasuredHeight(), widthMeasureSpec),
resolveSize(view.getMeasuredWidth(), heightMeasureSpec));
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (angleChanged) {
final RectF layoutRect = tempRectF1;
final RectF layoutRectRotated = tempRectF2;
layoutRect.set(0, 0, right - left, bottom - top);
rotateMatrix.setRotate(angle, layoutRect.centerX(), layoutRect.centerY());
rotateMatrix.postScale(-1, 1);
rotateMatrix.mapRect(layoutRectRotated, layoutRect);
layoutRectRotated.round(viewRectRotated);
angleChanged = false;
}
final View view = getView();
if (view != null) {
view.layout(viewRectRotated.left, viewRectRotated.top, viewRectRotated.right,
viewRectRotated.bottom);
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.save();
canvas.rotate(-angle, getWidth() / 2f, getHeight() / 2f);
canvas.scale(-1, 1);
super.dispatchDraw(canvas);
canvas.restore();
}
#Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
invalidate();
return super.invalidateChildInParent(location, dirty);
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
viewTouchPoint[0] = event.getX();
viewTouchPoint[1] = event.getY();
rotateMatrix.mapPoints(childTouchPoint, viewTouchPoint);
event.setLocation(childTouchPoint[0], childTouchPoint[1]);
boolean result = super.dispatchTouchEvent(event);
event.setLocation(viewTouchPoint[0], viewTouchPoint[1]);
return result;
}
}
The Mongolian vertically mirrored font still needs to be set somewhere else, though. I find it easiest to make a custom TextView to do it:
public class MongolNonRotatedTextView extends TextView {
// This class does not rotate the textview. It only displays the Mongol font.
// For use with MongolLayout, which does all the rotation and mirroring.
// Constructors
public MongolNonRotatedTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public MongolNonRotatedTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MongolNonRotatedTextView(Context context) {
super(context);
init();
}
// This class requires the mirrored Mongolian font to be in the assets/fonts folder
private void init() {
Typeface tf = Typeface.createFromAsset(getContext().getAssets(),
"fonts/MongolMirroredFont.ttf");
setTypeface(tf);
}
}
Then the custom ListView item xml layout can look something like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rlListItem"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.MongolNonRotatedTextView
android:id="#+id/tvListViewText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"/>
</RelativeLayout>
Known issues:
If you look carefully at the image below you can see faint horizontal and vertical lines around the text. Although this image comes from another developer's app, I am getting the same artifacts in my app when I use the rotated ViewGroup (but not when I use the rotated TextView). If anyone knows where these are coming from, please leave me a comment!
This solution does not deal with rendering the Unicode text. Either you need to use non-Unicode text (discouraged) or you need to include a rendering engine in your app. (Android does not support OpenType smartfont rendering at this time. Hopefully this will change in the future. iOS, by comparison does support complex text rendering fonts.) See this link for a Unicode Mongolian rendering engine example.
I want to be able to mirror my app so it can be viewed in the windshield of a vehicle.
My XML has several nested LinearLayouts, TextViews and ImageViews. Currently I'm transforming each one and although it is mirrored, the structure of the elements is not (what was at the top is now at the bottom).
I've been looking for days and so far have tried a couple of approaches that have failed.
An animation that uses a matrix to flip on the X axis kind of works, except that it either reverts back or it stays and doesn't update, which is no good for interacting with the app.
I just tried to create a custom LinearLayout extending the parent one, hoping that I could apply a Matrix in its onDraw() method but that gives me a blank screen (I had to set setWillNotDraw(false); to hit the onDraw()).
Well eventually I found a solution that works well for me (so far it's caused no issues for users).
My solution was to override dispatchDraw to scale the canvas in my custom LinearLayout. Then I just needed to flip the touch events by overriding dispatchTouchEvent:
public class CustomContainer extends LinearLayout {
public CustomContainer(Context context) {
super(context);
this.setWillNotDraw(false);
}
public CustomContainer(Context context, AttributeSet attrs) {
super(context, attrs);
this.setWillNotDraw(false);
}
public CustomContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setWillNotDraw(false);
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
// Flip the view canvas
if (MyHUDActivity.mHUDMode) canvas.scale(1,-1, getWidth(), getHeight()/2f);
super.dispatchDraw(canvas);
canvas.restore();
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
// If in HUD mode then flip the touch zones
if (MyHUDActivity.mHUDMode) event.setLocation(event.getX(), getHeight()-event.getY());
return super.dispatchTouchEvent(event);
}
}
You can use the new Animation api to deal with reverting back after the horizontal flip.
I am trying to create a custom ViewGroup, and I want to use it with a full screen application. I am using the "requestWindowFeature(Window.FEATURE_NO_TITLE)" to hide the title bar. The title bar is not showing, but it still consuming space on top of the window.
The image above was generated with the following code:
public class CustomLayoutTestActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
Button b = new Button(this);
b.setText("Hello");
CustomLayout layout = new CustomLayout(this);
layout.addView(b);
setContentView(layout);
}
}
public class CustomLayout extends ViewGroup {
public CustomLayout(Context context) {
super(context);
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i("CustomLayout", "changed="+changed+" l="+l+" t="+t+" r="+r+" b="+b);
final int childCount = getChildCount();
for (int i = 0; i < childCount; ++i) {
final View v = getChildAt(i);
v.layout(l, t, r, b);
}
}
}
(The full Eclipse project is here)
It is interesting to see that it is the Android that is given this space for my custom layout. I am setting the CustomLayout as the root layout of my Activity. In the Log in the "onLayout" is receiving "t=25", and that is what is pushing my layout down. What I don't know is what I am doing wrong that makes Android the "t=25" (which is exactly the height of the title bar).
I am running this code in the Android SDK 2.1, but I also happens in Android 2.2.
EDIT: If I change the CustomLayout class for some default layout (such as LinearLayout), the space disappears. Of course, the default layouts of Android SDK don't create the layout I am trying to create, so that is why I am creating one.
Although the layout I am creating is somewhat complex, this is the smallest code I could create reproducing the problem I have with my layout.
It's not a full answer, but in the meantime you can work around the problem by wrapping your custom layout in a <FrameLayout />
Also, it's worth noting that your layout extends beyond the bottom of the screen. It's shifted down by the title bar height (38 pixels in my emulator)
Edit: Got it. onLayout() (and the corresponding layout() method) specify that the coordinate are not relative to the screen origin, they're relative to the parent ( http://developer.android.com/reference/android/view/View.html#layout%28int,%20int,%20int,%20int%29 ). So the system is telling you that you're at relative coordinates (0, 38), and you're adding it when passing that down to your child, which means that you're saying that your child is at screen coordinates (0, 76), causing the gap.
What you actually want to do is:
v.layout(0, 0, r - l, b - t);
That will put your child Views aligned with the top left corner of your View, with the same width and height as your view.
I had the same issue with a FrameLayout in 2.2
I fixed it by adding android:layout_gravity="top" to the FrameLayout
I am doing an app with gallery with showing a few images, when I scroll the images, they move and jump after a certain point. How do I make them smooth? Any sample code would be of great help.
I had similar problem. Looks like it can be caused by changes in layout, e.g. if you change text in textview which has wrap_content width. This cases layout change and probably forces gallery to update itself and it snaps right on current item.
I was able to fix it by playing with layout, setting fixed sizes where I could etc. but I don't know about permanent and reliable solution
EDIT: also I found this hack if above doesn't work for you
http://www.unwesen.de/2011/04/17/android-jittery-scrolling-gallery/
I managed to solve this problem by overriding the onLayout() method in the Gallery parent and then ignoring any calls where the changed flag was not true.
public class MyGallery extends Gallery {
public MyGallery(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
super.onLayout(changed, l, t, r, b);
}
}
}
I found the above Gallery extends solution to work fairly well. However it was still causing some jitter. By simply overriding the onLayout method and look for number of views on screen I ended up with a "smooth as silk" Gallery view.
Note that I use this for a full screen slideshow effect.
public class SmoothGallery extends Gallery {
public SmoothGallery(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int viewsOnScreen = getLastVisiblePosition() - getFirstVisiblePosition();
if(viewsOnScreen <= 0)
super.onLayout(changed, l, t, r, b);
}
}
What is the simplest way to draw pixels, lines and circles on a View?
I want to move a cross cursor around, so nothing particularly intensive.
I thought I could extend SurfaceView and add it to an XML and it would just work, but it just appears black, however, when I look at the layout view of localmap.xml in eclipse, the graphics appear as expected.
Any ideas? My onDraw is never called on the emulator, and even calling invalidate on the class makes no difference. I shall keep trying but can anyone see anything I've missed? or is there a better way entirely?
Frink
localmap.xml contains the following (in a RelativeLayout)
<com.example.android.game.LocalMapView
android:id="#+id/localmap_map"
android:layout_width="fill_parent"
android:layout_above="#id/localmap_planettext"
android:layout_below="#id/header"/>
LocalMapView.java contains the following (amongst other things)
public class LocalMapView extends SurfaceView {
Paint mPaint = new Paint();
//Construct a LocalMapView based on inflation from XML
public LocalMapView(Context context, AttributeSet attrs) {
super(context, attrs);
// allow the map to receive the focus
setFocusable(true);
}
private void drawPixel(Canvas canvas, int x, int y, int colour) {
mPaint.setColor(colour);
if ((x >= MAP_MIN_X) && (x < MAP_MAX_X) && (y >= MAP_MIN_Y) && (y < MAP_MAX_Y)) {
canvas.drawPoint(((float)x * mScaleMapToScreenX), ((float)y * mScaleMapToScreenY), mPaint);
}
}
private void drawCircle(Canvas canvas, int x, int y, int radius, int colour) {
mPaint.setColor(colour);
canvas.drawCircle(((float)x), ((float)y), ((float)radius), mPaint);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircle(canvas, MAP_MAX_X/2, MAP_MAX_Y/2, 1, 0xFF00FFFF);
drawPixel(canvas, MAP_MAX_X/2, MAP_MAX_Y/2, 0xFF000000);
}
With SurfaceView you don't do the drawing in onDraw(). You have to grab a canvas from the underlying surface and draw in there. It seems to me you don't really know why you are using a SurfaceView. Just use a normal View instead and onDraw() will work just fine.