Android tip #028 – Sync two ScrollLayouts when scroll

Platform/Language: Java/XML/Android

Description: if we need to scroll two ScrollLayouts at the same time we can create a new View that implements the standard ScrollView in order to create a listener which allow us to know when this one is scrolling and modify the behavior of the other (or maybe because we want to know about the scroll behavior).

Code:

IScrollListener.java

1
2
3
4
5
6
7
public interface IScrollListener {

  void onScrollChanged(
    IScrollListener scrollView,
    int x, int y, int oldx, int oldy);

}

ObservableScrollView.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ObservableScrollView extends ScrollView {

  private IScrollListener listener = null;

  public ObservableScrollView(Context context) {
      super(context);
  }

  public ObservableScrollView(Context context,
           AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);
  }

  public ObservableScrollView(Context context,
           AttributeSet attrs) {
      super(context, attrs);
  }

  public void setScrollViewListener(IScrollListener listener) {
      this.listener = listener;
  }

  @Override
  protected void onScrollChanged(int x, int y,
           int oldx, int oldy) {
      super.onScrollChanged(x, y, oldx, oldy);
      if (listener != null) {
          listener.onScrollChanged(this, x, y, oldx, oldy);
      }
  }
}

Use two ObservableScrollView in your layout: oScrollViewOne, oScrollViewTwo.

YoutActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class YourActivity extends Activity
                          implements ScrollViewListener {
  ObservableScrollView oScrollViewOne, oScrollViewTwo;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    oScrollViewOne = (ObservableScrollView) this
        .findViewById(R.id.oScrollViewOne);
    oScrollViewTwo = (ObservableScrollView) this
        .findViewById(R.id.oScrollViewTwo);

    oScrollViewOne.setScrollViewListener(this);
    oScrollViewTwo.setScrollViewListener(this);
  }

  @Override
  void onScrollChanged(
    IScrollListener scrollView,
    int x, int y, int oldx, int oldy) {

    if (scrollView == oScrollViewOne) {
      oScrollViewTwo.scrollTo(x, y);
    } else if (scrollView == oScrollViewTwo) {
      oScrollViewOne.scrollTo(x, y);
    }
  }
}

Android tip #027 – Add shadow to labels, buttons and other views

Platform/Language: Java/XML/Android

Description: We can drop shadows to our controls (TextView, Buttons, Checkbox, …). Adding this shadow, we can create a better interface in our applications (but be careful adding shadow to everything!).

Result

Code:

my_layout.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- ... -->
<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
 
  android:textColor="@android:color/black"
  android:text="@string/eridemNet"
 
  android:shadowRadius="3"
  android:shadowDx="3"
  android:shadowDy="2"
  android:shadowColor="@android:color/black"
></TextView>
<!-- ... -->

Android tip #026 – Create nice buttons with XML

Platform/Language: XML/Android

Description: We can create nice buttons simply using few colors and gradients. We need to create a Selector resource and attach all the shape items for every state: pressed, focussed, disabled and normal. In the most common cases, focussed and normal could show the same result. In the case of pressed and normal, we will invert the colors. And in the case of disabled, we will use other colors (like a gray color).

Result

Custom nice button

Code:

colors.xml in /values/

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="NiceButtonStartColor">#4AA02C</color>
    <color name="NiceButtonEndColor">#348017</color>
    <color name="NiceButtonDisabledStartColor">#565051</color>
    <color name="NiceButtonDisabledEndColor">#736F6E</color>
    <color name="NiceButtonBorderColor">#254117</color>
</resources>

nice_button.xml in /drawables/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?xml version="1.0" encoding="utf-8"?>
<selector
   xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_pressed="true" >
        <shape>
            <gradient
               android:endColor="@color/NiceButtonStartColor"
               android:startColor="@color/NiceButtonEndColor"
               android:angle="270" />
            <stroke
               android:width="1dp"
               android:color="@color/NiceButtonBorderColor" />
            <corners
               android:radius="3dp" />
            <padding
               android:left="0dp"
               android:top="10dp"
               android:right="0dp"
               android:bottom="10dp" />
        </shape>
    </item>

    <item android:state_focused="true" >
        <shape>
            <gradient
               android:startColor="@color/NiceButtonStartColor"
               android:endColor="@color/NiceButtonEndColor"
               android:angle="270" />
            <stroke
               android:width="1dp"
               android:color="@color/NiceButtonBorderColor" />
            <corners
               android:radius="3dp" />
            <padding
               android:left="0dp"
               android:top="10dp"
               android:right="0dp"
               android:bottom="10dp" />
        </shape>
    </item>

  <item android:state_enabled="false">
          <shape>
            <gradient
               android:startColor="@color/NiceButtonDisabledStartColor"
               android:endColor="@color/NiceButtonDisabledEndColor"
               android:angle="270" />
            <stroke
               android:width="1dp"
               android:color="@color/NiceButtonBorderColor" />
            <corners
               android:radius="3dp" />
            <padding
               android:left="0dp"
               android:top="10dp"
               android:right="0dp"
               android:bottom="10dp" />
        </shape>
  </item>

    <item>        
        <shape>
            <gradient
               android:startColor="@color/NiceButtonStartColor"
               android:endColor="@color/NiceButtonEndColor"
               android:angle="270" />
            <stroke
               android:width="1dp"
               android:color="@color/NiceButtonBorderColor" />
            <corners
               android:radius="3dp" />
            <padding
               android:left="0dp"
               android:top="10dp"
               android:right="0dp"
               android:bottom="10dp" />
        </shape>
    </item>
</selector>

my_layout.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- ... -->
<!-- Better if you use most of the attributes in a style -->
<Button
  android:id="@+id/btnStart"
  android:layout_width="fill_parent" 
  android:layout_height="40dp"
  android:layout_margin="3dp"
  android:gravity="center"

  android:background="@drawable/nice_button"

  android:text="@string/labelStart"
  android:textColor="@android:color/white"
  android:textSize="18sp"
  android:shadowRadius="1"
  android:shadowDx="1"
  android:shadowDy="1"
  android:shadowColor="@android:color/black"
></Button>
<!-- ... -->

Android tip #025 – Launch Activity from a service

Platform/Language: Java/Android

Description: if we have a service in background and we need to launch an Activity in foreground, we need to add the tag FLAG_ACTIVITY_NEW_TASK to the Intent.

Code:

MyService.java

1
2
3
4
5
6
7
8
9
10
11
public class MyService extends Service {
  /* ... */

  private void launchActivity() {
    Intent intent = new Intent(this, MyActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    this.startActivity(intent);
  }

  /* ... */
}

Android tip #024 – Optimize lists a 175%

Platform/Language: Java/XML/Android

Related with: Optimize lists a 150%

Description: two operations are expensive when we create custom lists: Inflate (covered on the tip 23) and findByViewId(int). We can avoid call to this second operation saving the views used for every row in a wrapper.

This pattern implies use a Model and a Wrapper. The Model will save the information of every row and the Wrapper will save the Views. We need to save the wrapper in every row view using the properties getTag() and setTag(Object).

This example will use only one value in the model and a TextView in the wrapper, but you can extend it as much as you wish.

Code:

Model: Item.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Item {
  private String value = null;

  public String getValue() {
    if (this.value == null) {
      this.value = "";
    }
    return this.value;
  }

  public void setValue(String value) {
      this.value = value;
  }
}

Wrapper: ItemWrapper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ItemWrapper {
  private View row = null;
  private TextView mTextView = null;

  public ItemWrapper(View row) {
    this.mRow = row;
  }

  private TextView getMTextView() {
    if (this.mTextView == null) {
      this.mTextView = (TextView)
        this.row.findViewById(R.id.your_row_layout_textview);
    }
    return this.mTextView;
  }

  public void populate(Item item) {
    this.getMTextView().setText(item.getValue());
  }
}

Adapter: MyAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private List<Item> items;

/* ... */

@Override
public View getView(int position, View convertView,
                    ViewGroup parent) {
  View row = convertView;
  ItemWrapper wrapper = null;

  if (row == null) {
    LayoutInflater inflater = (LayoutInflater) this.mContext
        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    row = inflater.inflate(R.layout.your_row_layout, null);

    wrapper = new ItemWrapper(row);
    row.setTag(wrapper);   
  } else {
    wrapper = (ItemWrapper) row.getTag();
  }

  /* Populate row */
  wrapper.populate(items.get(position)); 

  return row;
}

Android tip #023 – Optimize lists a 150%

Platform/Language: Java/XML/Android

Related with: Optimize lists a 175%

Description: we can optimize the Adapters attached to a ListView inflating our custom layouts when it is necessary. This process is easy to implement with only one condition (Android will managed the rest of the work).

MyAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* ... */

@Override
public View getView(int position, View convertView,
                    ViewGroup parent) {
  View row = convertView;

  if (row == null) {
    LayoutInflater inflater = (LayoutInflater) this.mContext
        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    row = inflater.inflate(R.layout.your_row_layout, null);  
  }

  /* Populate row */
 
  return row;
}

/* ... */

Android tip #022 – Play alarm sound

Platform/Language: Java/Android

Description: we can play the user’s alarm sound in our application using the class RingtoneManager and MediaPlayer.

Code:

YourActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* ... */

private MediaPlayer playAlarmSound() {
  Uri alert = RingtoneManager.getDefaultUri(
              RingtoneManager.TYPE_ALARM);
  MediaPlayer mMediaPlayer = new MediaPlayer();
  mMediaPlayer.setDataSource(this, alert);

  AudioManager audioManager = (AudioManager)getSystemService(
                              Context.AUDIO_SERVICE);
  int volumen = audioManager.getStreamVolume(
                AudioManager.STREAM_ALARM);
 
  if (volumen != 0) {
    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
    mMediaPlayer.setLooping(true);
    mMediaPlayer.prepare();
    mMediaPlayer.start();
  }

  return mMediaPlayer; // We can stop it outside
}

Android tip #021 – SSH, execute remote commands with Android

Platform/Language: Java/XML/Android

Description: we can use some external libraries to create a SSH connection and execute commands on remote.

We need to download the JSCH libraries and JZLIB libraries that we can get directly on here:

Furthermore, we need to give INTERNET permission to our application in the manifest file.

In this example we execute the command ls in a remote machine using SSH and return the result. NOTE: you should improve the code for exception handling.

Code:

AndroidManifest.xml

1
2
3
4
<manifest ... >
 <!-- ... -->    
 <uses-permission android:name="android.permission.INTERNET" />
</manifest>

YourClass.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* ... */

public static String executeRemoteCommand(
                       String username,
                       String password,
                       String hostname,
                       int port) throws Exception {    
   
  JSch jsch = new JSch();
  Session session = jsch.getSession(username, hostname, 22);
  session.setPassword(password);
       
  // Avoid asking for key confirmation
  Properties prop = new Properties();
  prop.put("StrictHostKeyChecking", "no");
  session.setConfig(prop);

  session.connect();

  // SSH Channel
  ChannelExec channelssh = (ChannelExec)
                           session.openChannel("exec");      
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  channelssh.setOutputStream(baos);
       
  // Execute command
  channelssh.setCommand("ls");
  channelssh.connect();        
  channelssh.disconnect();
       
  return baos.toString();
}

/* ... */

Android tip #020 – Broadcast intents

Platform/Language: Java/Android

Description: we can send broadcast messages (Intents) in our application. They can be useful when we need to comunicate Activities with other Activities, Services with Activities and so on. It is a good way to send and listen messages in the whole application.

We need a receiver (which will listen the message) and a sender (which will send the message)

Code:

Sender.java

1
2
3
4
5
6
7
public static final String BROADCAST_MESSAGE = "my_message";

private void sendBroadcastMessage(Context context) {
  Intent i = new Intent();
  i.setAction(BROADCAST_MESSAGE);
  context.sendBroadcast(i);
}

ReceiverActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ReceiverActivity extends Activity {

  private BroadcastReceiver receiver = new BroadcastReceiver(){
    @Override
    public void onReceive(Context arg0, Intent arg1) {
      ReceiverActivity.this.doSomething();
    }
  };

  private void doSomething() {
  }

  public void onResume() {
    super.onResume();

    IntentFilter filter = new IntentFilter();
    filter.addAction(Sender.BROADCAST_MESSAGE);

    this.registerReceiver(this.receiver, filter);
  }

  public void onPause() {
    super.onPause();

    this.unregisterReceiver(this.receiver);
  }

  /* ... */
}

Android tip #019 – Database not open

Platform/Language: Java/XML/Android

Description: A very common error when we are working with database is not check that we have the database opened. So, we will receive the exception “java.lang.IllegalStateException: database not open“. We can fix it with a simple condition before we execute any query.

Code:

YourClass.java

1
2
3
4
5
6
7
8
9
10
11
12
SQLiteDatabase d = null;

// Initialization, other queries and so on...
// ...

if (d == null || !d.isOpen()) {
  d = db.getWritableDatabase();
  // d = db.getReadableDatabase();
}

// Your query here
// ...