Android tip #028 – Sync two ScrollLayouts when scroll
Jun 4, 2011 Android Tips
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
Jun 3, 2011 Android Tips
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
Jun 3, 2011 Android Tips
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

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
Apr 18, 2011 Android Tips
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%
Apr 9, 2011 Android Tips
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%
Apr 9, 2011 Android Tips
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
Apr 1, 2011 Android Tips
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
Mar 30, 2011 Android Tips
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
Mar 28, 2011 Android Tips
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
Mar 28, 2011 Android Tips
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 // ... |
