Pull To Refresh is an exciting topic in Android programming. Pull to refresh is the hottest android UI pattern used by developers in their applications to improve the usability. You can see this pull to refresh feature in many android applications like Gmail, Facebook, Twitter etc…
As you can search on Internet, there are many libraries help us to dealing with this problem, I also had a post about using a library written by Chris Banes in Eclipse project format, you can take a glance.
Moreover, with Material Design technology, Google has released
SwipeRefreshLayout
, that working well, although it don't have many customs. You can see how to use it at this POST.In this tutorial I’ll show you how to use pull to refresh feature in your android application by using an another powerful external library called Ultra-Pull-To-Refresh, it support all containers and widgets.
This post is part of a series called Android TOP useful libraries
Importing library to Android project
app/build.gradle
and add this lib dependency:
dependencies {
compile 'in.srain.cube:ultra-ptr:1.0.11'
}
XML declaring
PtrClassicFrameLayout
or PtrClassicFrameLayout
to the activity layout file (xml) to use pull to refresh feature. These classes extend ViewGroup
, so you can put any view containers (FrameLayout
, RelativeLayout
,...) or widgets (ListView
, GridView
,...) inside them.Important NOTE: Both of containers can contain only one directly child view.
For example, creating an activity layout like this:
activity_main.xml
<in.srain.cube.views.ptr.PtrClassicFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:cube_ptr="http://schemas.android.com/apk/res-auto"
android:id="@+id/pt_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
cube_ptr:ptr_duration_to_close="200"
cube_ptr:ptr_duration_to_close_header="1000"
cube_ptr:ptr_keep_header_when_refresh="true"
cube_ptr:ptr_pull_to_fresh="false"
cube_ptr:ptr_ratio_of_header_height_to_refresh="1.2"
cube_ptr:ptr_resistance="1.7">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin"
android:paddingTop="100dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.35"
android:text="@string/country"
android:textStyle="bold" />
<TextView
android:id="@+id/city"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.65"
android:textColor="@android:color/holo_green_light" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.35"
android:text="@string/location"
android:textStyle="bold" />
<TextView
android:id="@+id/location"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.65"
android:textColor="@android:color/holo_blue_dark" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.35"
android:text="@string/temperature"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textStyle="bold" />
<TextView
android:id="@+id/temperature"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.65"
android:textColor="@android:color/holo_orange_dark" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.35"
android:text="@string/humidity"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textStyle="bold" />
<TextView
android:id="@+id/humidity"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.65"
android:textColor="@android:color/holo_purple" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.35"
android:text="@string/pressure"
android:textStyle="bold" />
<TextView
android:id="@+id/pressure"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.65"
android:textColor="@android:color/holo_red_dark" />
</LinearLayout>
</LinearLayout>
</in.srain.cube.views.ptr.PtrClassicFrameLayout>
Coding project
AsyncTask
to get data from Internet after user pull the layout, the view will be refreshed when AsyncTask
done:
GetWeatherTask.java
Your package info.devexchanges.ultrapulltorefresh;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetWeatherTask extends AsyncTask<Void, Void, String> {
private HttpURLConnection urlConnection;
private final String JSON_URL = "http://api.openweathermap.org/data/2.5/weather?q=hanoi,vn&appid=44db6a862fba0b067b1930da0d769e98";
private MainActivity activity;
private ProgressDialog progressDialog;
public GetWeatherTask(MainActivity activity) {
this.activity = activity;
progressDialog = ProgressDialog.show(activity, "Connecting...", "Downloading JSON...", true);
}
@Override
protected String doInBackground(Void... params) {
StringBuilder result = new StringBuilder();
try {
URL url = new URL(JSON_URL);
urlConnection = (HttpURLConnection) url.openConnection();
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
urlConnection.disconnect();
}
return result.toString();
}
@Override
protected void onPostExecute(String result) {
progressDialog.dismiss(); //dismiss dialog
activity.parsingJSON(result); //call back data to UI thread
}
}
Activity
must implements PtrHandler
interface and there are 2 methods need to override:onRefreshBegin()
: when user release pulling action, this will be invoked.checkCanDoRefresh()
: set anable/disable for pull to refresh feature.
Activity
:
MainActivity.java
Strings resource for this project:
package info.devexchanges.ultrapulltorefresh;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import org.json.JSONException;
import org.json.JSONObject;
import java.math.BigDecimal;
import java.math.RoundingMode;
import in.srain.cube.views.ptr.PtrClassicFrameLayout;
import in.srain.cube.views.ptr.PtrFrameLayout;
import in.srain.cube.views.ptr.PtrHandler;
public class MainActivity extends AppCompatActivity implements PtrHandler {
private PtrClassicFrameLayout pullToRefreshLayout;
private TextView location;
private TextView city;
private TextView pressure;
private TextView humidity;
private TextView temperature;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
temperature = (TextView) findViewById(R.id.temperature);
city = (TextView) findViewById(R.id.city);
location = (TextView) findViewById(R.id.location);
humidity = (TextView) findViewById(R.id.humidity);
pressure = (TextView) findViewById(R.id.pressure);
pullToRefreshLayout = (PtrClassicFrameLayout) findViewById(R.id.pt_frame);
//set handler for pull to refresh layout
pullToRefreshLayout.setPtrHandler(this);
//set last update time in header
pullToRefreshLayout.setLastUpdateTimeRelateObject(this);
}
@SuppressLint("SetTextI18n")
public void parsingJSON(String data) {
// parsing json and set the custom dialog components - text, image and button
try {
JSONObject jsonObject = new JSONObject(data);
//get location
JSONObject locationObject = jsonObject.getJSONObject("coord");
location.setText("[ " + locationObject.getString("lon") + ", " + locationObject.getString("lat") + " ]");
//get temperature, humidity and pressure
JSONObject tempObject = jsonObject.getJSONObject("main");
temperature.setText(kelvinToCelcius(tempObject.getString("temp")) + " " + (char) 0x00B0 + "C");
humidity.setText(tempObject.getString("humidity") + "%");
pressure.setText(hPaToatm(tempObject.getString("pressure")) + " atm");
//get city name
city.setText(jsonObject.getString("name"));
} catch (JSONException e) {
e.printStackTrace();
}
pullToRefreshLayout.refreshComplete(); //stop Refreshing
}
@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
return true;
}
@Override
public void onRefreshBegin(PtrFrameLayout frame) {
new GetWeatherTask(this).execute();
}
public double kelvinToCelcius(String kelvin) {
BigDecimal bd = new BigDecimal(Double.parseDouble(kelvin) - 273);
bd = bd.setScale(2, RoundingMode.HALF_UP);
return bd.doubleValue();
}
public double hPaToatm(String hPa) {
BigDecimal bd = new BigDecimal((Double.parseDouble(hPa) / 1013.25));
bd = bd.setScale(2, RoundingMode.HALF_UP);
return bd.doubleValue();
}
}
strings.xml
Running application, you'll have this result:<resources>
<string name="app_name">"Ultra Pull To Refresh"</string>
<string name="notice">Pull down to refresh</string>
<string name="location">Location</string>
<string name="country">City:</string>
<string name="temperature">Temperature:</string>
<string name="humidity">Humidity:</string>
<string name="pressure">Pressure:</string>
<string name="weather">Weather</string>
</resources>
Customizing Pull To Refresh Header
strings.xml
like this:
strings.xml
And this is new output:<resources>
<string name="app_name">"Ultra Pull To Refresh"</string>
<string name="notice">Pull down to refresh</string>
<string name="location">Location</string>
<string name="country">City:</string>
<string name="temperature">Temperature:</string>
<string name="humidity">Humidity:</string>
<string name="pressure">Pressure:</string>
<string name="weather">Weather</string>
<!--Customizing pull to refresh layout header texts -->
<string name="cube_ptr_pull_down_to_refresh">Kéo xuống để làm mới</string>
<string name="cube_ptr_release_to_refresh">Thả để làm mới</string>
<string name="cube_ptr_refreshing">Đang cập nhật...</string>
<string name="cube_ptr_refresh_complete">Đã cập nhật!</string>
<string name="cube_ptr_pull_down">Kéo xuống</string>
<string name="cube_ptr_last_update">Cập nhật lần cuối: </string>
<string name="cube_ptr_seconds_ago"> giây trước</string>
<string name="cube_ptr_minutes_ago"> phút trước</string>
<string name="cube_ptr_hours_ago"> giờ trước</string>
</resources>
Of course, if you want to make more complicated header, use
setHeader(View view)
of PtrFrameLayout
or PtrClassicFrameLayout
with view is inflating from a XML file. For example:
LayoutInflater inflater = (LayoutInflater)getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View header = inflater.inflate(R.layout.layout_header, null);
pullToRefreshLayout.setHeaderView(header);