
Android Tips
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

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> <!-- ... --> |
Cleaning unused Android resources
On middle/big projects we usually add lots of resources that we can use on code files, layouts or other resources. When we start to do modifications to the project, sometimes we forgot to remove unused strings, drawables, layouts and so on.
I wanted to create a script to check these unused resources on our Android project, but I found a project which already does this task. This project is called android-unused-resources (good name to google it), and we can download it from:
http://code.google.com/p/android-unused-resources/
The use is simple, add the file to the root project and execute it:
Check missing strings for a multi-language Android application
When we are creating a multi-language application for Android, we need to create several folders to save the strings of different languages (see Android tip #014 – Multi-language application). When we are handling hundred or thousand of strings, we can forget adding a string in all language files.
Looking for a tool to organize all the strings in my project and check if any string was missing, I found the W.F.M.H’s blog were you can download a PHP script to help you for checking all your strings.
The usage is easy (using ES language as example)
You could get a result similar to:
File: values-es/strings.xml
------------------------------------------------------
header_title
description_label
Missing in EN (you probably shall remove it from your <LANG> file)
File: values/strings.xml
------------------------------------------------------------------
mysection_label
Summary
----------------
BASE file: 'values/strings.xml'
LANG file: 'values-de/strings.xml'
2 missing strings in your LANG file.
1 obsolete strings in your LANG file.
For more information about the project, visit the main website (recommended):
W.F.M.H.’s blog, Android translators’ helper tool
Or download the script directly from his/her website: strings-check.zip
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(); } /* ... */ |

