preface

Posted on my personal blog:
Manual implementation of the wheel broadcast chart (2) : cyclic rolling, timing switch and indicators


In the previous article manual implementation of the rotation graph (A) : ViewPager introduction practice, we know the ViewPager layout, but also a simple start.

In this next article, we will move further in the direction of rote graphics.

At the end of the original article, I used Glide to load Gif images as the content of the wheel, so now I continue based on that code.

If you are unfamiliar with this section, you are advised to look at the code in the warehouse address at the end of the article

In this article we will implement:

  • circularly

  • Switching indicator

  • Timer switch

So let’s get to work.

# 1. Loop scrolling

ViewPager, while useful, doesn’t natively support looping, which means you:

  • The first one to the left jumps to the last one

  • Swiping right from the last one will bring back the first one

In the effect that we did, the first one can’t slide any further left, and the last one can’t slide any further right. In this way, the rotation chart is not “rotation”.

So we need to implement the loop scrolling effect ourselves.

How do you do that? At present, there are three mature methods:

Multi-page pseudo-loop

  • Create lots of pages, even if we only have 5 pages when we really need to show them

    • We place the starting point in the middle of the queue, and if we get to the first page we’re going to show you, as we move to the left, we set the next page to the last one

    • So no matter the user to the left or right slide, as long as it is normal, the user is not the end of the slide, resulting in a visual cycle

      • In normal apps, even if you use a page queue like this, the user doesn’t have the patience to keep sliding down

Suppose we now create a ViewPager of 1000 pages, and we actually need to show only 5 pages, so the result is as follows:

We set the first page to 500, and the user had to swipe 499 to get to the end.

Is the performance going to be bad?

  • Don’t

Because it says “create 1000” pages, we’re actually just telling the ViewPager Adapter that we’re going to use this many pages, not that he’s going to create this many pages.

We will return 1000 in the Adapter’s getCount method, which is only used to help the Adapter get the correct position, but is not actually created. (Read the PagerAdapter source code.)

Remember that the FragmentPagerAdapter will create three pages by default, so only three pages will be created, and any pages above or below that will be reclaimed.

There are two other implementations

We mainly use the first one, which is easy to understand and has no obvious drawbacks.

The other two methods are described in the following article: Android implements true ViewPager…

Now that we’ve introduced the implementation idea, we can start implementing it.

Open mainActivity.java and modify the code as follows:

public class MainActivity extends AppCompatActivity { private static final int MAX_NUMBER = 1000; private static final int START_POSITION = MAX_NUMBER/2; . @Override protected void onCreate(Bundle savedInstanceState) { ... mViewPager.setAdapter(new FragmentPagerAdapter(fm) { private int mIndex; @Override Public Fragment getItem(int position) {mIndex = math.abs (position - START_POSITION) % mStringList.length;if(position < START_POSITION && mIndex ! = 0){ mIndex = mStringList.length - mIndex; }return PageFragment.newInstance(mIndex);
            }
​
            @Override
            public int getCount() {
                returnMAX_NUMBER; }}); mViewPager.setCurrentItem(START_POSITION); . }}Copy the code

  • Define two constants, respectively

    • MAX_NUMBER: total number of pages, 1000 in total

    • START_POSITION: the starting page, starting at 500th in the middle

mIndex = Math.abs(position - START_POSITION) % mStringList.length;
                
if(position < START_POSITION && mIndex ! = 0){ mIndex = mStringList.length - mIndex; }Copy the code
  • Calculate the distance between the current position and START_POSITION, and then mod the result with the actual number of pages to display (temporarily using the length of mStringList instead)

    • The distance is positive or negative, so we’re taking the absolute value. But if you just take the absolute value and then mod it, instead of going 1->5 -> 4 when you swipe left, you go 1-> 2 ->3. This is the result of the mod, and if you’re not familiar with it, you can remember the result of the mod

    • So we added judgment

      • The page position is greater than the starting position, which is mod by relative distance

      • If it is less than the starting position, you can achieve the reciprocal effect by subtracting the remainder from the actual page count

@Override
public int getCount() {
    return MAX_NUMBER;
}Copy the code
  • This tells the Adapter how many pages there are

Remember to set the start page:

mViewPager.setCurrentItem(START_POSITION);Copy the code

Now our loop is complete. Try it.

2. Page indicators

Many multicast graphs have a small indicator that identifies the current page. Let’s do one right now.

Given the previous loop scrolling, the principle of such a page indicator should be easy to understand.

Idea is:

  • Creating control styles

    • Selected style

    • An unchecked style

  • Add controls to a view

  • Modify the style of the indicator as the page slides

Creating control styles

In the res/drawable folder, create two files:

Normal style:

dot_normal.xml

<? xml version="1.0" encoding="utf-8"? > <shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <size android:width="5dp"
        android:height="5dp"/>
    <solid android:color="@android:color/holo_red_dark"/>
</shape>Copy the code

Selected style:

dot_selected.xml

<? xml version="1.0" encoding="utf-8"? > <shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <size android:width="5dp"
        android:height="5dp"/>
    <solid android:color="@color/colorPrimary"/>
</shape>Copy the code

Then add a LinearLayout to activity_main. XML and use code to add the dots:

<? xml version="1.0" encoding="utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
​
    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager_inside"
        android:layout_width="400dp"
        android:layout_height="400dp"
        android:background="@android:color/darker_gray"
        android:layout_centerInParent="true">
    </android.support.v4.view.ViewPager>
    
    <LinearLayout
        android:id="@+id/ll_inside"
        android:layout_below="@+id/view_pager_inside"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:orientation="horizontal"
        android:gravity="center"/>
​
</RelativeLayout>Copy the code

Add controls to a view

The code here comes from the Android ViewPager infinite loop swiping left and right (automatic) implementation.

Back in mainactivity.java,

public class MainActivity extends AppCompatActivity { private List<TextView> mTextViews; private LinearLayout mLinearLayout; . @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); mViewPager = findViewById(R.id.view_pager_inside); mLinearLayout = findViewById(R.id.ll_inside); initCircle(); . }... private voidinitCircle() {
        mTextViews = new ArrayList<>();
        int d = 20;
        int m = 7;
​
        for (int i = 0; i < mStringList.length; i++){
            TextView textView = new TextView(this);
            if (i == 0){
                textView.setBackgroundResource(R.drawable.dot_selected);
            }else{ textView.setBackgroundResource(R.drawable.dot_normal); } LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(d, d); params.setMargins(m, m, m, m); textView.setLayoutParams(params); mTextViews.add(textView); mLinearLayout.addView(textView); }}... }Copy the code

  • Define two variables

    • MTextViews: A list of small points

      • Our little dot is actually made of TetxView, and the background color is set to be circular

    • MLinearLayout: References the LinearLayout layout you just created

  • Create an initCIrcle() method

    • Create TextView views in code, setting properties such as width, margin, and background for each view

      • The background styles are the two.xml files you just created

    • Add the dots to the layout using the addView method

After being called in the Oncreate() method, we see that the dots have already appeared.

Now we need to modify the styles based on the page to act as indicators.

public class MainActivity extends AppCompatActivity {
    
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
​
            }
​
            @Override
            public void onPageSelected(int position) {
                changePoints(position % mStringList.length);
            }
​
            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });  
    }
    
    private void changePoints(int pos){
        if(mTextViews ! = null){for (int i = 0; i < mTextViews.size(); i++){
                if (pos == i){
                    mTextViews.get(i).setBackgroundResource(R.drawable.dot_selected);
                }else {
                    mTextViews.get(i).setBackgroundResource(R.drawable.dot_normal);
                }
            }
        }
    }
}Copy the code
  • Add a state for mViewPager listener ViewPager. OnPageChangeListener

    • Override the onPageSelected() method: this method is called when the page is selected

    • Within this method, we call the changePoint() method to change the style of the indicator

When we call changePoint(), we pass in position % mStringList.length. There is a problem here.

Mstring.length is a multiple of mString.length if you just use position to model mString.length, this is fine because our starting position (500) is a multiple of mString.length. So it’s going to start at 0. But if we change the starting position later or change the number of images we show, this will be a mistake.

So we’ll get the index the same way we did before. Modify onPageSelected() method:

private int mIndex;
​
@Override
public void onPageSelected(int position) {
    mIndex = Math.abs(position - START_POSITION) % mStringList.length;
    if(position < START_POSITION && mIndex ! = 0){ mIndex = mStringList.length - mIndex; } changePoints(mIndex); }Copy the code

I’ll just use this code for convenience. If you have time, you can optimize it yourself to improve the reuse rate.

Logically, it should be done by now.

3. Play regularly

One of the characteristics of the round – cast map is the timing of the playback.

With all the effects we’ve achieved, timing should be a piece of cake.

We can use Handle to call setCurrentItem().

The following code idea comes from the Android ViewPager infinite loop swiping left and right (automatic) implementation.

Modify our mainactivity.java

private Handler mHandler = new Handler();
​
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); . mHandler = new Handler(); mHandler.postDelayed(new TimerRunnable(),5000); } class TimerRunnable implements Runnable{ @Override public voidrun() {
            int curItem = mViewPager.getCurrentItem();
            mViewPager.setCurrentItem(curItem+1);
            if(mHandler! =null){ mHandler.postDelayed(this,5000); } } } @Override protected voidonDestroy() { super.onDestroy(); mHandler = null; // Recycle the Activity when it exits}Copy the code

4. Modify the transition animation

Call the ViewPager. SetPageTransformer () method can set the animation itself.

Let’s start with a new animation class, PhotoTransformer. Java

package me.rosuh.android.viewpagernew;
​
import android.support.annotation.NonNull;
import android.support.v4.view.ViewPager;
import android.view.View;
​
​
public class PhotoTransformer implements ViewPager.PageTransformer {
    @Override
    public void transformPage(@NonNull View page, float position) {
        int pageWidth = page.getWidth();
​
        if (position < -1){
            page.setAlpha(1);
        }else if(position <= 1){ page.setPivotX(position < 0f ? page.getWidth() : 0f); Page. SetPivotY (page. GetHeight () * 0.5 f); page.setRotationY(90f * position); }else{ page.setAlpha(1); }}}Copy the code

Then set the animation for mViewPager:

. FragmentManager fm = getSupportFragmentManager(); mViewPager.setPageTransformer(true, new PhotoTransformer());
​
mViewPager.setAdapter(...)Copy the code

To set this animation, it’s best to set the shadow property of CardView to 0. Then modify the layout slightly. (Not listed here, you can go to the code repository to see for yourself). Here’s how it looks:

conclusion

Project Address
ViewPagerDemo.


So far, our rotation chart is ready.

These two articles are aimed at beginners, so there is room for improvement in many areas.

But it doesn’t stop us from getting to it.

After all, the author of this article is not experienced, the level is limited, so the omission is inevitable, I hope that the seniors who read this article don’t hesitate to give advice, thank you ~


Thanks for reference articles and resources:

  • android-viewpager-transformers

  • Android implements true ViewPager…

  • Android ViewPager swipes left and right in an infinite loop (automatic)