Using Android HorizontalScrollView to create Android Horizontal Carousel

In this tutorial, we are going to learn how to use android HorizontalScrollView in android app. There are many occasions where you might have need to use Android HorizontalScrollView in your app like if you are planning to implement android horizontal carousel.

What is android HorizontalScrollView?

According to android documentation – “Layout container for a view hierarchy that can be scrolled by the user, allowing it to be larger than the physical display. A HorizontalScrollView is a FrameLayout, meaning you should place one child in it containing the entire contents to scroll; this child may itself be a layout manager with a complex hierarchy of objects. A child that is often used is a LinearLayout in a horizontal orientation, presenting a horizontal array of top-level items that the user can scroll through.

The TextView class also takes care of its own scrolling, so does not require a HorizontalScrollView, but using the two together is possible to achieve the effect of a text view within a larger container.

HorizontalScrollView only supports horizontal scrolling. For vertical scrolling, use either ScrollView or ListView.”

As stated in the documentation above, horizontal ScrollView implementation can also be achieved by using RecyclerView or ListView, I will suggest you read the android Horizontal ScrollView with RecyclerView and android Horizontal ScrollView with ListView I wrote.

We are going to learn how to create android app using HorizontalScrollView by implementing an android horizontal carousel.

We will use the HorizontalScrollView in the bottom part of the main layout and a ViewPager in the top. When an image thumbnail in the HorizontalScrollView is clicked, it will display a lager image in the View Pager.

The android ViewPager will use a FragmentPageAdapter to bind individual Fragment class that will represent each image to be displayed in the ViewPager.

Before we dive into more details, it is important for us to understand what we are planning to achieve. Below is the screen-shot of the application we will be creating.

Android HorizontalScrollView

Lets start to soil our hands in code. Start up your IDE. For this tutorial, I am using the following tools and environment, feel free to use what works for you.

Windows 10

Android Studio

Sony Xperia ZL

Min SDK 14

Target SDK 23

To create a new android application project, follow the steps as stipulated below.

Go to File menu

Click on New menu

Click on Android Application

Enter Project name: AndroidHorizontalScrollView

Package: com.inducesmile.androidhorizontalscrollview

Select Blank Activity

Name your activity : MainActivity

Keep other default selections

Continue to click on next button until Finish button is active, then click on Finish Button.

Strings.xml

We are going to update our project strings.xml file located in the values folder inside the res folder. Open the file and add the code below to it.

<string name="app_name">Android HorizontalScrollView</string>
<resources>  <string name="app_name">Android HorizontalScrollView</string> </resources>

Colors.xml

Open the colors.xml file in the same location as the strings.xml file and add the code below to the file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
    <color name="colorBlack">#000000</color>
    <color name="colorDivider">#B6B6B6</color>
</resources>

Manifest.xml

Since we are going to store our thumbnail images in android external storage and read the file before they will be displayed in our HorizontalScrollView, we are going to add two user permission – WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE.

Note that these two permissions are considered dangerous permission, so we will require user permission in run-time.

Open the Manifest.xml file and add the code below to the file.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.inducesmile.androidhorizontalscrollview">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

activity_main.xml

Lets move over to our application main layout. As mentioned above, we are going to add a ViewPager and HorizontalScrollView controls. The HorizontalScrollView will contain a LinearLayout where all the images in the external storage will be added.

Open the activity_main.xml and paste the code below to the file.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.inducesmile.androidhorizontalscrollview.MainActivity">
    <android.support.v4.view.ViewPager
        android:id="@+id/large_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/horizontal_scrollview"
        android:layout_alignParentTop="true">
    </android.support.v4.view.ViewPager>
    <HorizontalScrollView
        android:id="@+id/horizontal_scrollview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true">
        <LinearLayout
            android:id="@+id/my_gallery"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
        </LinearLayout>
    </HorizontalScrollView>
</RelativeLayout>

MainActivity.java

The MainActivity class will instantiate the View controls in the main layout file. We will create a FragmentPagerAdapter and pass the object of the class in the setAdapter() method of the ViewPager class.

The thumbnail images are copied from the drawable folder to the android external storage and the images are retrieved from the external storage and added to the LinearLayout inside the HorizontalScrollView.

Open the MainActivity.java file if it is not open in your IDE, copy and paste the code below to the file.

import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private ViewPager largeViewPager;
    private CustomFragmentPagerAdapter customFragmentPageAdapter;
    private LinearLayout galleryLayout;
    private String photoPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "photos";
    private String[] fileList;
    private Uri[] mUrls;
    private int currentIndex;
    private int[]resourceImages;
    private File directoryPath;
    private final int REQUEST_CAMERA_PERMISSION = 200;
    private File storeDirectory;
    protected Bitmap resourceBitmap;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        int[] largeDrawableImages = new int[]{R.drawable.thumbnailb, R.drawable.letterc, R.drawable.thumbnaile, R.drawable.thumbnailf};
        largeViewPager = (ViewPager)findViewById(R.id.large_image);
        customFragmentPageAdapter = new CustomFragmentPagerAdapter(getSupportFragmentManager(), largeDrawableImages);
        largeViewPager.setOffscreenPageLimit(3);
        largeViewPager.setAdapter(customFragmentPageAdapter);
        galleryLayout = (LinearLayout)findViewById(R.id.my_gallery);
        directoryPath = getAlbumStorageDir("images");
        /*if(directoryPath.exists() && directoryPath.isDirectory()) {
            try {
                FileUtils.cleanDirectory(directoryPath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }*/
        resourceImages = new int[]{R.drawable.thumbnailb, R.drawable.thumbnailc, R.drawable.thumbnaile, R.drawable.thumbnailf};
        for(int i = 0; i < resourceImages.length; i++){
            resourceBitmap = resourceToImageBitmap(resourceImages[i]);
            saveFileInExternalStorage(resourceBitmap, i);
        }
        System.out.println("Files number: " + directoryPath.listFiles().length);
        File[] filterStoredFiles =  directoryPath.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String filename) {
                return ((filename.endsWith(".jpg"))||(filename.endsWith(".png")));
            }
        });
        if(filterStoredFiles.length <= 0){
            Toast.makeText(MainActivity.this, "Whoops!!!, There is no file saved in the external storage", Toast.LENGTH_LONG).show();
            return;
        }
        fileList = new String[filterStoredFiles.length];
        for(int i = 0; i < fileList.length; i++){
            fileList[i] = filterStoredFiles[i].getAbsolutePath();
        }
        mUrls = new Uri[fileList.length];
        for(int i=0; i < fileList.length; i++){
            mUrls[i] = Uri.parse(fileList[i]);
        }
        for(int j = 0; j < mUrls.length; j++){
            String imageAbsolutePath = mUrls[j].toString();
            ImageView addImageView = getNewImageView(imageAbsolutePath);
            final int indexJ = j;
            addImageView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    currentIndex = indexJ;
                    Toast.makeText(MainActivity.this, "Current index " + currentIndex, Toast.LENGTH_LONG).show();
                    largeViewPager.setCurrentItem(currentIndex);
                }
            });
            galleryLayout.addView(addImageView);
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_CAMERA_PERMISSION) {
            if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
                // close the app
                Toast.makeText(MainActivity.this, "Sorry!!!, you can't use this app without granting this permission", Toast.LENGTH_LONG).show();
                finish();
            }
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(null != galleryLayout){
            galleryLayout.removeAllViews();
        }
    }
    @Override
    protected void onResume() {
        super.onResume();
        Log.e(TAG, "onResume");
        if(storeDirectory.exists()){
            storeDirectory.delete();
        }
        System.out.println("Files " + storeDirectory.getAbsolutePath());
        FileOutputStream out = null;
        try {
            storeDirectory.createNewFile();
            out = new FileOutputStream(storeDirectory);
            resourceBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private void saveFileInExternalStorage(Bitmap bitmap, int index){
        if(!isExternalStorageWritable() && !isExternalStorageReadable()){
            Toast.makeText(MainActivity.this, "There is no external storage in your device or not writable", Toast.LENGTH_LONG).show();
            return;
        }
        String filename = "thumbnail" + String.valueOf(index) + ".jpg";
        storeDirectory = new File(directoryPath, filename);
        // Add permission for camera and let user grant the permission
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CAMERA_PERMISSION);
            return;
        }
        if(storeDirectory.exists()){
            storeDirectory.delete();
        }
        //System.out.println("Files " + storeDirectory.getAbsolutePath());
        Log.i(TAG, "Directory not created");
        FileOutputStream out = null;
        try {
            storeDirectory.createNewFile();
            out = new FileOutputStream(storeDirectory);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private Bitmap resourceToImageBitmap(int fileResource){
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), fileResource);
        return bitmap;
    }
    private ImageView getNewImageView(String photoPath){
        Bitmap bm = decodeFile(photoPath);
        ImageView imageView = new ImageView(getApplicationContext());
        imageView.setLayoutParams(new LinearLayout.LayoutParams(300, 200));
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imageView.setImageBitmap(bm);
        return imageView;
    }
    public static Bitmap decodeFile(String photoPath){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(photoPath, options);
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inPreferQualityOverSpeed = true;
        return BitmapFactory.decodeFile(photoPath, options);
    }
    public File getAlbumStorageDir(String albumName) {
        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), albumName);
        if (!file.mkdirs()) {
            Log.e(TAG, "Directory not created");
        }
        return file;
    }
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }
    public boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }
}

CustomFragmentPagerAdapter.java

Now, we are going to create a CustomFragmentPagerAdapter class. This class will inherit from the android FragmentPagerAdapter class. The getItem(int position) and getCount() methods of the super class will be overridden.

Go to your package folder, right click on the folder and select create a new java file. Name the file CustomFragmentPagerAdapter. Add the code below to this class.

import android.support.v4.app.Fragment;

import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
public class CustomFragmentPagerAdapter extends FragmentPagerAdapter{
    private int[] largeImages;
    public CustomFragmentPagerAdapter(FragmentManager fragmentManager, int[] largeImages){
        super(fragmentManager);
        this.largeImages = largeImages;
    }
    @Override
    public Fragment getItem(int position) {
        System.out.println("Current position" + position);
        return ImageGalleryFragment.newInstance(position, this.largeImages);
    }
    @Override
    public int getCount() {
        return largeImages.length;
    }
}

ImageGalleryFragment.java

We are going to create a Fragment subclass. This class is used for binding by the CustomFragmentPagerAdapter class we created previously. Following the same process we used to create the class but now we will name the new java file ImageGalleryFragment.java.

We are going to use this single Fragment class to load each individual image attached to the ViewPager through its adapter. Open this file and add the below code to the file.

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
public class ImageGalleryFragment extends Fragment {
    private static final String ARGUMENT = "index";
    private static int[] largeImages;
    private int indexNumber;
    public  static ImageGalleryFragment newInstance(int index, int[] largeImage){
        ImageGalleryFragment imageGalleryFragment = new ImageGalleryFragment();
        Bundle args = new Bundle();
        args.putInt(ARGUMENT, index);
        imageGalleryFragment.setArguments(args);
        largeImages = largeImage;
        return imageGalleryFragment;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        indexNumber = getArguments() != null ? getArguments().getInt(ARGUMENT) : 1;
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.image_display, container, false);
        ImageView mImageView = (ImageView) v.findViewById(R.id.display_image);
        int imageResource = largeImages[indexNumber];
        Bitmap mBitmap = resourceToImageBitmap(imageResource);
        mImageView.setImageBitmap(mBitmap);
        return mImageView;
    }
    private Bitmap resourceToImageBitmap(int fileResource){
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), fileResource);
        return bitmap;
    }
}

image_display.xml

Lets go ahead a create a layout file that the Fragment class inflates. This layout file will only contain an ImageView inside a parent LinearLayout.

Go to the res folder, inside the res folder right click on the layout folder and choose New > layout XML file. Name this layout file image_display.xml.

Open the new layout file and add the below code to the layout file.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ImageView
        android:id="@+id/display_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@string/app_name"/>
</LinearLayout>

This brings us to the end of this tutorial. I hope that you have learn something. Run your app as see for yourself.

You can download the code for this tutorial below. If you are having hard time downloading the tutorials, kindly contact me.

Remember to subscribe with your email address so that you will be among the first to receive my new android blog post once it is published.

Please if you love this tutorial, kindly download my android app – Complete Mathematics – in Google Play Store and let me know what you think about it.

One Response

Add a Comment