Thursday, November 25, 2010

How to disable title bar in portrait / Landscape mode

This post talks about how to hide/show title bar in portrait and landscape.

There are scenarios where we might want to display a title bar in portrait and not in landscape due to space limitations in landscape.

How do we do this ??

- Every time a screen is rotated from landscape to portrait or vice versa, the activity is destroyed and created again.

- In onCreate(), check the screen sizes and decide it is in portrait or Landscape.

- Based on the mode, set the Window attribute Window.FEATURE_NO_TITLE using requestWindowFeature() api.

Note that requestWindowFeature() api needs to be called before setting the content view.



onCreate()
{
setTitleBar(); --> Call to decide to set whether title bar or not.
setContentView(R.layout.main);
}

public void setTitleBar()
{
Display display = ((WindowManager)mContext.getSystemService(mContext.WINDOW_SERVICE)).getDefaultDisplay();

int width = display.getWidth();
int height = display.getHeight();


if(width > height)
{
/* In Landscape */
requestWindowFeature(Window.FEATURE_NO_TITLE);
}

}

Wednesday, November 10, 2010

Using canvas to draw a text whereve a click is made on screen.

How to draw a text wherever a click is made.

- A custom view is implemented, which overrides onDraw() function call.

- To get the touch point (x,y) on screen, override the onTouchEvent() and check for action ACTION_DOWN and update the global variables x,y.

- Later in onDraw(), on the canvas object supplied from framework, use the drawText api to draw the text on the touched point.

- A paint object is initialised with the color and font size values. Use this in drawText() api.



drawText(String text, float x, float y, Paint paint)
Draw the text, with origin at (x,y), using the specified paint.



- Create a instance of this custom view and set this as setContentView() of activity to display this view.



imagemove.java
---------------



package com.android.imagemove;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;

public class imagemove extends Activity {

public class DisplayView extends View
{
int positionX = 5;
int positionY = 15;
Paint mPaint;
DisplayView(Context context)
{
super(context);
mPaint = new Paint();
mPaint.setTextSize(25);
mPaint.setColor(0xFF0000FF);
mPaint.setTextSize(16);

}
@Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN: {
positionX = (int)event.getX();
positionY = (int)event.getY();

invalidate();
}
}
return true;
}
@Override
public void onDraw(Canvas canvas)
{
System.out.println("X & Y"+positionX+":"+positionY);
canvas.drawText("Welcome", positionX, positionY, mPaint);
}
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DisplayView view = new DisplayView(this);
setContentView(view);
}

}

Monday, November 8, 2010

How to apply animations when activity enters & exits in framework.??

Every time when an activity is started / entered first time, or when an activity is exited, a simple animation is performed by the framework.Currently framework performs simple fade-in and fade-out animations.

What if we like to apply our own animations when activity screen is first launched.?

It is possible. I will point out the places where you need to modify to apply you animations.

1 - Through xml

If you like to apply animations through xml then write your animation logics in these files.


frameworks/base/core/res/res/anim/activity_open_enter.xml
frameworks/base/core/res/res/anim/activity_open_exit.xml
frameworks/base/core/res/res/anim/activity_close_enter.xml
frameworks/base/core/res/res/anim/activity_open_exit.xml


These animations are given style item names in styles.xml



<style name="Animation.Activity">
<item name="activityOpenEnterAnimation">@anim/activity_open_enter</item>
<item name="activityOpenExitAnimation">@anim/activity_open_exit</item>
<item name="activityCloseEnterAnimation">@anim/activity_close_enter</item>
<item name="activityCloseExitAnimation">@anim/activity_close_exit</item>


These names are referred in frameworks while picking the resource id for animations.
ex: com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation
com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;

2 - Custom animation class.

frameworks/base/services/java/com/android/server/WindowManagerService.java

This file in services of framework is where animation is set and performs the animations for activity enter & exit.
There are two places animations are set one for activities and one for windows, where activity view is attached. Parent of all views.


private boolean applyAnimationLocked(AppWindowToken wtoken,
WindowManager.LayoutParams lp, int transit, boolean enter) --> Activity

private boolean applyAnimationLocked(WindowState win,
int transit, boolean isEntrance) ----> Windows


I added logs in both places and found that in applyAnimationLocked for activity



// Only apply an animation if the display isn't frozen. If it is
// frozen, there is no reason to animate and it can cause strange
// artifacts when we unfreeze the display if some different animation
// is running.


It dint enter into the main 'if' to set the animations. I think it is because of the above said reason.!!

So i set the animation for main window itself in the other 'applyAnimationLocked' for windows.

For testing, i took the Rotate3dAnimation.java class provided in android-sdk samples under API-Demos folder.

Create a instant of this class and set the animation.



Added the below code at line no 2739

Display display = ((WindowManager)mContext.getSystemService(mContext.WINDOW_SERVICE)).getDefaultDisplay();

int width = display.getWidth();
int height = display.getHeight();

final float centerX = width.getWidth() / 2.0f;
final float centerY = height.getHeight() / 2.0f;

// Create a new 3D rotation with the supplied parameter
// The animation listener is used to trigger the next animation
final Rotate3dAnimation rotation =
new Rotate3dAnimation(0, 0, centerX, centerY, 310.0f, true);
rotation.setDuration(500);
rotation.setFillAfter(true);
a = rotation;

win.setAnimation(a);
win.mAnimationIsEntrance = isEntrance;



This animation will be applied to all switch cases check,,which is window enter, window exit.. etc..



switch (transit) {
case WindowManagerPolicy.TRANSIT_ENTER:
attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
break;
case WindowManagerPolicy.TRANSIT_EXIT:
attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
break;
case WindowManagerPolicy.TRANSIT_SHOW:
attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
break;
case WindowManagerPolicy.TRANSIT_HIDE:
attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
break;



Now you can observe animations are applied to all types of windows...like dialog, error notes, all activitie..!!

By this you can define your own animation extending 'Animation', it can be even 3D animation and can be applied to activity enter and exit... !!

Hope it helps someone who works in framework and trying to apply animation when activity enters....!!

Sunday, November 7, 2010

Invisible activity - Why status bar is not focusable ?

I was in a situation to launch a keyboard, when a tap happens on the edittext on the status bar.
I introduced a editText in the status bar. And expected framework to launch the keyboard when tapped. But unfortunately framework doesnt popup the keyboard.

I posted in android-developers forums and got some clue by android framework engineers.

1 - When status bar view is added to windowmanager, windowManager layoutparams is set with the flag 'WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE '.

frameworks/base/services/java/com/android/server/status/StatusBarService.java



public void systemReady() {
System.out.println("Statusbar service - systemReady ");
final StatusBarView view = mStatusBarView;
WindowManager.LayoutParams lp = new
WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
view.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height),
WindowManager.LayoutParams.TYPE_STATUS_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING,
mPixelFormat);
lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
lp.setTitle("StatusBar");
lp.windowAnimations = R.style.Animation_StatusBar;
WindowManagerImpl.getDefault().addView(view, lp);
}


FLAG_NOT_FOCUSABLE Window flag: this window won't ever get key
input focus, so the user can not send key or other button events to
it.

http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_NOT_FOCUSABLE

2 - It is decided by the framework that status bar will not receive any focusable events. You can handle the touch, but it will not get any focus.!!

- I tried uncommenting that flag and checked. Then status bar window received focus, where as the other windows mainly - phone Window, where the activity window resides doesnt receive focus, so literally i couldnt do anything on the screen....:)

for more details refer here

http://groups.google.com/group/android-platform/browse_thread/thread/385fa0ede79fd7f8/e286f2ff0e6f1c16#e286f2ff0e6f1c16

So i decided to a write a invisible activity :)

Means like, if somebody taps on the status bar's edit text, i launch an activity and through that activity i launch the keyboard, giving user an illusion that keyboard is launched because of the tap on the edit text.

how do we achieve this ???

- To pop up the keyboard automatically when the activity is launched, we need a editText component in the activity.

- A runnable is created and it will be executed after the activity is launched to show the inputmethod (IME) using inputManager.Which gives user a illusion that keyboard is launched. Keyboard can be launched with any View element, Button, TextView, EditText..etc...But View has to be in foucs



@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// Launch the IME after a bit
mHandler.postDelayed(mShowInputMethodTask, 0); --> Launch the runnable
}
}

private Runnable mShowInputMethodTask = new Runnable() { -->Runnable
public void run() {
showInputMethodForQuery();
}
};

protected void showInputMethodForQuery() { -----> Shows the IME using a View
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(press, 0);
}
}



Lets see the complete example:

1- Create a simple View element in the layout.xml.. In this case i have created a button.

To make it invisible.

Apply the background, text property to color:transparent

android:textColor="@android:color/transparent"
android:background="@android:color/transparent"


Layout.xml:
------------



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>

<Button android:id="@+id/press"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/transparent"
android:background="@android:color/transparent"
android:text="@string/hello"/>

</LinearLayout>



Note:

When the keyboard is launched and when the user presses 'back' button, there is no way that inputmethodManager will let the application know that keyboard is exited.
So when back is pressed, keyboard will be dismissed and you could see a transparent window, where u cannot really do anything.

Basically it is the transparent activity which has button in it which is also a transparent one due to the settings of background color & text color.

So we need to handle the onTouchEvent() of activity and do an exit. ( this.finish() )



invisibleactivity.java
--------------------------



package com.android.urldisplay;

import android.app.Activity;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.view.Display;
import android.widget.LinearLayout;
import android.view.ViewGroup;
import android.view.Gravity;

public class invisibleactivity extends Activity
{

private static final int APP_ID = 0;
private Handler mHandler = new Handler();
Button press;

private Runnable mShowInputMethodTask = new Runnable() {
public void run() {
showInputMethodForQuery();
}
};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);


}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// Launch the IME after a bit
mHandler.postDelayed(mShowInputMethodTask, 0);
}
}
protected void showInputMethodForQuery() {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(press, 0);
}
}
public boolean onTouchEvent (MotionEvent event)
{
this.finish();
return true;
}
}




- In this approach we only launched the keyboard. But our aim was to handle the text typed in the keyboard and show it back to the editText on the status bar.

So for that instead of button we need to have a editText and launch the keyboard for this view (EditText ). and when the user presses 'Go' or 'Done' button get the text from this editText.

Will show you how to handle the button press on keyboard in coming blogs :)

How to dismiss a custom dialog based on touch points?


When we write a custom dialog class or set theme style/Theme.Dialog to activity, we might be interested in dismissing the dialog if user touches outside the dialog visible area.

This can be achieved through a special class, android.graphics.Region. It has a api contains(x,y) which is used to tell you if the passed x,y falls in the region defined.

1 - we need to override the public boolean onTouchEvent (MotionEvent event) function and get the current x,y the user touched on the screen.

2 - Define the region top left x,y and bottom right x,y of the rectangle region which you are interested in listening the touch events.

Region dialogRegion = new Region (10,12,183,179 );

3 - Dismiss the dialog using finish() if the touch points falls within this region() co-ordinates.

if( dialogRegion.contains(x, y) == true)
this.finish();

Here is the complete example.

This example shows that activity is set style - Theme.Dialog.


<activity android:name=".touch"
android:label="@string/app_name"
android:theme="@android:style/Theme.Dialog">


Size of the dialog is set using WindowManager layout params.

Depending on the height & width, i have set the region points.And in onTouchEvent() i check for the touch points, if within the region() points i simply call finish() which exits the activity.

touch.java
------------


package com.android.touch;

import android.app.Activity;
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.WindowManager;

public class touch extends Activity {
Region dialogRegion;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WindowManager.LayoutParams params = getWindow().getAttributes();

params.height = 200;
params.width = 200;


this.getWindow().setAttributes(params);

dialogRegion = new Region (10,12,183,179 );

}
public boolean onTouchEvent (MotionEvent event)
{

final int action = event.getAction();


final int x = (int) event.getX();
final int y = (int) event.getY();

switch (action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN: {
System.out.println("Action down"+x +"::"+y);
if( dialogRegion.contains(x, y) == true)
this.finish();

}
}

return true;
}
}