Creating a skinny ProgressBar

Ok.. So I want to create something like this: (for no reason :P)

It turned out to be a really simple procedure:



First of all to make it customizable (colors-wise)

Create progress_bar_states.xml in your res/drawable folder:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        
        <item android:id="@android:id/background">
            <shape>
                <gradient android:angle="270" android:centercolor="#0b131e" android:centery="0.75" android:endcolor="#0d1522" android:startcolor="#000001">
            </gradient>
        </shape>
        
        <item android:id="@android:id/secondaryProgress">
            <clip>
                <shape>
                    <gradient android:angle="270" android:centercolor="#234" android:centery="0.75" android:endcolor="#a24" android:startcolor="#234">
                </gradient>
            </shape>
        </clip>
        
        <item android:id="@android:id/progress">
            <clip>
                <shape>
                    <gradient android:angle="270" android:centercolor="#0b1f3c" android:centery="0.75" android:endcolor="#06101d" android:startcolor="#144281">
                </gradient>
            </shape>
        </clip>
        
    </item>
</item></item></layer-list>


You don't need to worry about the 'secondaryProgress'.
Keep in mind that 'progress' is the color of the selected part and background is the color behind the whole progressbar.

Now Download this `progresstip.png` picture and add it to your res/drawable folder:
<



Now We have the drawables we need.
To create the progress bar, we need a small layout xml
I called it progress.xml:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
	<LinearLayout 
	   android:id="@+id/linearLayout1"
	   android:layout_width="match_parent"
	   android:layout_height="match_parent"
	   android:orientation="vertical"
	   android:paddingTop="8dip"
	   android:paddingBottom="8dip">
	<ProgressBar
		android:id="@+id/progressBar"
		android:progressDrawable="@drawable/progress_bar_states"
		android:layout_width="fill_parent"
		android:layout_height="8dip"
		style="?android:attr/progressBarStyleHorizontal"
		android:indeterminateOnly="false"
		android:max="100"
		/>
	</LinearLayout>
	<LinearLayout 
	   android:id="@+id/LL"
	   android:layout_width="match_parent"
	   android:layout_height="match_parent"
	   android:orientation="vertical">
	<ImageView android:layout_width="24dip" android:src="@drawable/progresstip"
		android:layout_height="24dip" android:id="@+id/imageView" />
	</LinearLayout>
</merge>


This has <merge> It will simply merge the progress bar with the image so that they appear above each other.

Now We are ready to include this progressBar in our Activity.

Remember that should be included inside a

layout/main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/frameLayout1">
	<include layout="@layout/progress"/>
	</FrameLayout>
</LinearLayout>

Now if we create an Activity and set its content to main.xml

MainActivity.java
public class MainActivity extends Activity {
	ProgressBar progressBar;
	ImageView imageView;
	LinearLayout LL;
	Context mContext = this;
	@Override
	public void onCreate(Bundle bundle){
		super.onCreate(bundle);
		setContentView(R.layout.main);
	}
}

Nothing will move! To make it move we will set the OnTouchListener of the progress bar.

So final: MainActivity.java

public class MainActivity extends Activity {
	ProgressBar progressBar;
	ImageView imageView;
	LinearLayout LL;
	@Override
	public void onCreate(Bundle bundle){
		super.onCreate(bundle);
		setContentView(R.layout.main);
		progressBar = (ProgressBar) findViewById(R.id.progressBar);
		imageView = (ImageView) findViewById(R.id.imageView);
		LL = (LinearLayout) findViewById(R.id.LL);
		progressBar.setOnTouchListener(new OnTouchListener(){

			@Override
			public boolean onTouch(View arg0, MotionEvent arg1) {
				progressBar.setProgress((int)(((double)arg1.getX()/(double)arg0.getWidth())*((int)((ProgressBar)arg0).getMax())));
				
				LL.removeAllViews();
				LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(imageView.getLayoutParams());
				params.setMargins((int)((double)arg1.getX())-imageView.getWidth()/2, 0, 0, 0);
				LL.addView(imageView,params);
				return true;
			}
			
		});
	}
}

Now if you run the activity, everything work great, the circle follows the progress selected. However at the start of it, it is not possible to set a progress , an initial progress.

How can this be acheived?

Using one boolean and overriding of a function

Inside MainActivity.java
	boolean firstTime = true;
	@Override
	public void onWindowFocusChanged(boolean hasFocus){
		super.onContentChanged();
		if(hasFocus&&firstTime){
			firstTime=false;
			int initialProgress = 50;
			progressBar.setProgress(initialProgress);
			double loc = ((double)initialProgress/(double)progressBar.getMax())*(double)progressBar.getWidth();
			LL.removeAllViews();
			LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(imageView.getLayoutParams());
			params.setMargins((int)(loc)-imageView.getWidth()/2, 0, 0, 0);
			LL.addView(imageView,params);
		}
	}

Click here for Sample Project + Source Code (download)

2 comments:

  1. Thanks for this tutorial! I am looking to apply this to a seekbar. I am not really sure why you adjust the progress the way you do. Cant you just set the progress rather than delete/create/append the thumb each time?

    ReplyDelete
  2. Setting the progress here is actually done using the seekbar setprogress (to set the seekbar progress) and using the LayoutParams setMargin (to set the tip location)
    If you could setMargin without removeView and addView with new LayoutParams, it would really be a very small constant improvement in the performance

    ReplyDelete