Android Loop Tabs Sample

Published by Igor Khrupin on

Here is sample for loop tabs in Android.

Video with sample application in action

Lets show how it works.

1. I have Tab enum which describe Tab element. Also you can handle tab state. I don’t do it in this sample.

package com.hrupin.looptabs.infinitetabbar;

import com.hrupin.looptabs.R;

/**
 * Created by Igor Khrupin www.hrupin.com on 03/05/16.
 */
public enum Tab {
    TAB_1(R.drawable.ic_tab_1_act, R.drawable.ic_tab_1, 0),
    TAB_2(R.drawable.ic_tab_2_act, R.drawable.ic_tab_2, 1),
    TAB_3(R.drawable.ic_tab_3_act, R.drawable.ic_tab_3, 2);

    private final int resActive;
    private final int resInctive;
    private final int index;

    public int getResActive() {
        return resActive;
    }

    public int getResInctive() {
        return resInctive;
    }

    public int getIndex() {
        return index;
    }

    Tab(int resActive, int resInctive, int index) {
        this.resActive = resActive;
        this.resInctive = resInctive;
        this.index = index;
    }
}

2. Project has View for tabs which extended from TabCloneableView. Here you can work around tab state. And tab transition between tabs.

package com.hrupin.looptabs.infinitetabbar;

import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.hrupin.looptabs.R;

/**
 * Created by Igor Khrupin www.hrupin.com on 03/05/16.
 */
public class TabView extends TabCloneableView {

    private static final String TAG = "TabView";

    public enum State {
        INACTIVE, INACTIVE_PRESSED, ACTIVE, ACTIVE_PRESSED;
    }

    private Tab tab;
    private State state = State.INACTIVE;
    private ImageView ivInActive;
    private ImageView ivActive;
    private TextView tvIndex;

    public TabView(Context context) {
        super(context);
        View.inflate(context, R.layout.item_tab, this);
        this.ivInActive = (ImageView) findViewById(R.id.iv_inactive);
        this.ivActive = (ImageView) findViewById(R.id.iv_active);
        this.tvIndex = (TextView) findViewById(R.id.index);
    }

    public TabView clone() {
        TabView vw = new TabView(this.getContext());
        vw.switchState(State.INACTIVE);
        vw.setId((int) (Math.random() * 10000));
        vw.setTag(this.getTag());
        vw.setTab(this.getTab());
        return vw;
    }

    public void setTab(Tab tab) {
        this.tab = tab;
        this.ivActive.setImageResource(tab.getResActive());
        this.ivInActive.setImageResource(tab.getResInctive());
        this.tvIndex.setText(tab.getIndex() + "");
    }

    public Tab getTab() {
        return tab;
    }

    public State getState() {
        return state;
    }

    public void switchState(State state) {
        this.state = state;
        switch (state) {
            case ACTIVE:
                this.ivActive.setAlpha(1f);
                this.ivInActive.setAlpha(0f);
                break;
            case INACTIVE:
                this.ivActive.setAlpha(0f);
                this.ivInActive.setAlpha(1f);
                break;
            case ACTIVE_PRESSED:
                this.ivActive.setAlpha(0.6f);
                this.ivInActive.setAlpha(0f);
                break;
            case INACTIVE_PRESSED:
                this.ivActive.setAlpha(0f);
                this.ivInActive.setAlpha(0.6f);
                break;
        }
    }

    public void setPositionOffset(boolean isNext, float positionOffset) {
        float activeAlpha = positionOffset;
        float inActiveAlpha = 1f - positionOffset;
        if (isNext) {
            this.ivActive.setAlpha(activeAlpha);
            this.ivInActive.setAlpha(inActiveAlpha);
        } else {
            this.ivActive.setAlpha(inActiveAlpha);
            this.ivInActive.setAlpha(activeAlpha);
        }
    }

    @Override
    public String toString() {
        return "TV{" +
                "t=" + tab +
                //", state=" + state +
                ",i=" + tab.getIndex() +
                '}';
    }
}

3. TabCloneableView class

package com.hrupin.looptabs.infinitetabbar;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;

/**
 * Created by Igor Khrupin www.hrupin.com on 03/05/16.
 */

public abstract class TabCloneableView extends RelativeLayout {

	public TabCloneableView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}
	public TabCloneableView(Context context, AttributeSet attrs) {
		super(context,attrs);
		
		// TODO Auto-generated constructor stub
	}
	public abstract TabCloneableView clone();
}

4. The most of magic located in the TabInfiniteScrollView. This View contains TabView objects and work with them.

package com.hrupin.looptabs.infinitetabbar;

import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;

import com.hrupin.looptabs.looppager.LoopViewPager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by Igor Khrupin www.hrupin.com on 03/05/16.
 */

public class TabInfiniteScrollView extends FrameLayout implements View.OnTouchListener {
    private static final String TAG = "TabInfiniteScrollView";

    private final static int SWIPE_OR_CLICK_ACTION_THRESHHOLD = 150;

    private static boolean touchDown = false;
    private final float tabWidth;

    private Context context;
    private ArrayList<TabCloneableView> carouselItems;
    private TabCloneableView replicaF, replicaB;
    private int itemCount = 0;
    private float scrollX;
    private float firstX;
    private float firstY;
    private TabSize itemSize;
    private boolean isScrolling = false;
    private LoopViewPager viewPager;

    public TabInfiniteScrollView(Context ctx, TabSize itemsize) {
        super(ctx);
        context = ctx;
        itemSize = itemsize;
        tabWidth = itemSize.width;
        RelativeLayout.LayoutParams sparams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        this.setClipChildren(true);
        this.setLayoutParams(sparams);
        this.setOnTouchListener(this);
        carouselItems = new ArrayList<TabCloneableView>();
        setHorizontalScrollBarEnabled(false);
    }

    @Override
    public boolean onInterceptHoverEvent(MotionEvent event) {
        //return super.onInterceptHoverEvent(event);
        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        //return super.onInterceptTouchEvent(ev);
        return false;
    }

    @Override
    public boolean onTouch(View arg0, MotionEvent arg1) {
        switch (arg1.getAction()) {
            case MotionEvent.ACTION_DOWN:
                firstX = arg1.getX();
                firstY = arg1.getY();
                touchDown = true;
                if (isLeftTab()) {
                    //TODO handle tab state
                } else if (isRightTab()) {
                    //TODO handle tab state
                } else {
                    //TODO handle tab state
                }
                return true;
            case MotionEvent.ACTION_UP: {
                touchDown = false;
                float finishX = arg1.getX();
                float finishY = arg1.getY();
                if (isAClick(firstX, finishX, firstY, finishY)) {
                    if (isLeftTab()) {
                        swipeLeft(true);
                    } else if (isRightTab()) {
                        swipeRight(true);
                    }
                } else {
                    if (isLeftTab()) {
                        //TODO handle tab state
                    } else if (isRightTab()) {
                        //TODO handle tab state
                    } else {
                        //TODO handle tab state
                    }
                }
                firstX = 0;
                firstY = 0;
            }
            case MotionEvent.ACTION_OUTSIDE: {
                if (isLeftTab()) {
                    //TODO handle tab state
                } else if (isRightTab()) {
                    //TODO handle tab state
                } else {
                    //TODO handle tab state
                }
            }
            case MotionEvent.ACTION_MOVE:
                float finishX = arg1.getX();
                if (touchDown) {
                    float differenceX = firstX - finishX;
                    if (Math.abs(differenceX) > SWIPE_OR_CLICK_ACTION_THRESHHOLD) {
                        if (differenceX > 0) {
                            swipeRight(true);
                        } else {
                            swipeLeft(true);
                        }
                    }
                }
                break;
        }
        return false;
    }

    private boolean isRightTab() {
        return firstX > 2 * tabWidth && firstX < 3 * tabWidth;
    }

    private boolean isLeftTab() {
        return firstX > 0 && firstX < tabWidth;
    }

    public void swipeLeft(boolean triggerViewPager) {
        swipe(tabWidth);
        if(triggerViewPager) {
            viewPager.swipeLeft();
        }
    }

    public void swipeRight(boolean triggerViewPager) {
        swipe(-tabWidth);
        if(triggerViewPager) {
            viewPager.swipeRight();
        }
    }

    private void swipe(final float delta) {
        if (!isScrolling) {
            isScrolling = true;
            final float step = delta / 10f;
            scrollX = step;
            new ScrollTimerTask().run();
            final Handler handler = new Handler();
            final java.util.concurrent.atomic.AtomicInteger n = new AtomicInteger(1);
            final Runnable counter = new Runnable() {
                @Override
                public void run() {
                    if (n.getAndIncrement() == 11) {
                        isScrolling = false;
                        touchDown = false;
                        firstX = 0;
                        scrollX = 0;
                    } else {
                        new ScrollTimerTask().run();
                        handler.postDelayed(this, 10);
                    }
                }
            };
            handler.postDelayed(counter, 10);
        }
    }

    private boolean isAClick(float startX, float endX, float startY, float endY) {
        float differenceX = Math.abs(startX - endX);
        float differenceY = Math.abs(startY - endY);
        if (differenceX > SWIPE_OR_CLICK_ACTION_THRESHHOLD || differenceY > SWIPE_OR_CLICK_ACTION_THRESHHOLD) {
            return false;
        }
        return true;
    }

    public void setViewPager(LoopViewPager viewPager) {
        this.viewPager = viewPager;
    }

    public class ScrollTimerTask implements Runnable {
        public synchronized void arrangeViews() {
            if (Math.abs(scrollX) > 0.1f) {
                //Log.i(TAG, "arrangeViews");
                synchronized (carouselItems) {
                    ((Activity) context).runOnUiThread(new Runnable() {
                        public void run() {
                            Collections.sort(carouselItems,
                                    new Comparator<View>() {
                                        @Override
                                        public int compare(View lhs, View rhs) {
                                            FrameLayout.LayoutParams lhsParams = (FrameLayout.LayoutParams) lhs.getLayoutParams();
                                            FrameLayout.LayoutParams rhsParams = (FrameLayout.LayoutParams) rhs.getLayoutParams();
                                            return ((Integer) lhsParams.leftMargin).compareTo((Integer) rhsParams.leftMargin);
                                        }
                                    });
                            for (int i = 0; i < carouselItems.size(); i++) {
                                // arrange
                                TabCloneableView vw = carouselItems.get(i);
                                FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) vw.getLayoutParams();
                                if (i == 0) {
                                    params.leftMargin += scrollX;
                                } else {
                                    FrameLayout.LayoutParams fparams = (FrameLayout.LayoutParams) carouselItems.get(0).getLayoutParams();
                                    params.leftMargin = (int) (fparams.leftMargin + (i * itemSize.width));
                                }
                                if (vw != replicaF && vw != replicaB) {
                                    if (scrollX > 0) {
                                        if (params.leftMargin > ((itemCount - 1) * itemSize.width) && params.leftMargin <= (itemCount * itemSize.width)) {
                                            FrameLayout.LayoutParams rparams = null;
                                            if (replicaF == null) {
                                                replicaF = vw.clone();
                                                rparams = new FrameLayout.LayoutParams((int) itemSize.width, (int) itemSize.height);
                                                rparams.leftMargin = (int) -itemSize.width;
                                                replicaF.setLayoutParams(rparams);
                                            }
                                            if (TabInfiniteScrollView.this.findViewById(replicaF.getId()) == null) {
                                                TabInfiniteScrollView.this.addView(replicaF);
                                                carouselItems.add(0, replicaF);
                                            }
                                        }
                                        if (params.leftMargin >= (itemCount * itemSize.width)) {
                                            if (replicaF != null) {
                                                if (TabInfiniteScrollView.this.findViewById(replicaF.getId()) != null) {
                                                    TabInfiniteScrollView.this.removeView(replicaF);
                                                    carouselItems.remove(replicaF);
                                                    carouselItems.remove(vw);
                                                    carouselItems.add(0, vw);
                                                    i = 0;
                                                    params.leftMargin = 0;
                                                }
                                            }
                                            replicaF = null;
                                        }
                                    } else {
                                        if (params.leftMargin < 0 && params.leftMargin >= -itemSize.width) {
                                            FrameLayout.LayoutParams rparams = null;
                                            if (replicaB == null) {
                                                replicaB = vw.clone();
                                                rparams = new FrameLayout.LayoutParams((int) itemSize.width, (int) itemSize.height);
                                                rparams.leftMargin = (int) ((itemCount - 1) * itemSize.width);
                                                replicaB.setLayoutParams(rparams);
                                            }
                                            if (TabInfiniteScrollView.this.findViewById(replicaB.getId()) == null) {
                                                TabInfiniteScrollView.this.addView(replicaB);
                                                carouselItems.add(replicaB);
                                            }
                                        }
                                        if (params.leftMargin < -itemSize.width) {
                                            if (replicaB != null) {
                                                if (TabInfiniteScrollView.this.findViewById(replicaB.getId()) != null) {
                                                    TabInfiniteScrollView.this.removeView(replicaB);
                                                    carouselItems.remove(replicaB);
                                                    carouselItems.remove(vw);
                                                    carouselItems.add(vw);
                                                    i = 0;
                                                    params.leftMargin = (int) (itemCount * itemSize.width);
                                                }
                                            }
                                            replicaB = null;
                                        }
                                    }
                                }
                                vw.setLayoutParams(params);
                            }

                            if (replicaF != null) {
                                FrameLayout.LayoutParams fparams = (FrameLayout.LayoutParams) replicaF.getLayoutParams();
                                if (fparams.leftMargin < -itemSize.width || fparams.leftMargin > 0) {
                                    if (TabInfiniteScrollView.this.findViewById(replicaF.getId()) != null) {
                                        replicaF.getLayoutParams();
                                        TabInfiniteScrollView.this.removeView(replicaF);
                                        carouselItems.remove(replicaF);
                                    }
                                    replicaF = null;
                                }
                            }
                            Collections.sort(carouselItems,
                                    new Comparator<View>() {
                                        @Override
                                        public int compare(View lhs, View rhs) {
                                            FrameLayout.LayoutParams lhsParams = (FrameLayout.LayoutParams) lhs.getLayoutParams();
                                            FrameLayout.LayoutParams rhsParams = (FrameLayout.LayoutParams) rhs.getLayoutParams();
                                            return ((Integer) lhsParams.leftMargin).compareTo((Integer) rhsParams.leftMargin);
                                        }

                                    });
                            Log.i(TAG, carouselItems + "");
                        }
                    });
                }
            }

        }

        @Override
        public void run() {
            arrangeViews();
        }
    }

    public void addItem(TabCloneableView vw) {
        FrameLayout.LayoutParams rparams = new FrameLayout.LayoutParams((int) itemSize.width, (int) itemSize.height);
        rparams.leftMargin = (int) (carouselItems.size() * itemSize.width);
        vw.setLayoutParams(rparams);
        super.addView(vw, carouselItems.size());
        carouselItems.add(vw);
        itemCount = carouselItems.size();
    }
}

5. TabSize class

package com.hrupin.looptabs.infinitetabbar;

/**
 * Created by Igor Khrupin www.hrupin.com on 03/05/16.
 */

public class TabSize {
	float height;
	float width;
	public TabSize(float w, float h){
		width = w;
		height = h;
	}
	
}

6. And the Activity

package com.hrupin.looptabs;

import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.Window;

import com.hrupin.looptabs.infinitetabbar.TabBar;
import com.hrupin.looptabs.looppager.LoopViewPager;
import com.hrupin.looptabs.looppager.TabAdapter;

/**
 * Created by Igor Khrupin www.hrupin.com on 03/05/16.
 */

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener {

    private static final String TAG = "MainActivity";

    private LoopViewPager viewPager;
    private TabAdapter adapter;
    private int tabWidth;
    private int tabHeight;
    private TabBar tabBar;
    private int lastPage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getSupportActionBar().hide();

        viewPager = (LoopViewPager) findViewById(R.id.pager);
        viewPager.setBoundaryCaching(true);
        adapter = new TabAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        tabBar = (TabBar) findViewById(R.id.tabbar);

        initTabs();
    }

    private void initTabs() {
        DisplayMetrics displaymetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
        int screenWidth = displaymetrics.widthPixels;
        tabWidth = screenWidth/3;
        tabHeight = getResources().getDimensionPixelSize(R.dimen.bar_height);

        tabBar.post(new Runnable() {
            @Override
            public void run() {
                  tabBar.init(MainActivity.this, tabWidth, tabHeight, viewPager);
            }
        });

        viewPager.setOnPageChangeListener(this);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        if(lastPage > position) {
            //User Move to left
            tabBar.swipeLeft();
        }else if(lastPage < position) {
            //User Move to right
            tabBar.swipeRight();
        }
        lastPage=position;
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
}

Full source here:

Download it from github

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.