Android Advanced Drawing - Custom View Fully Mastered

This is the second blog in the Custom View series. Let's continue to learn about Custom View.
Today, let's implement the ad banner case.
We want to achieve such an effect.

To achieve this effect, we can use the ViewPager control, and then add some custom controls to complete. So let's start now.
Create a new android project.
Modify the activity_main.xml file.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.itcast.test0429.MainActivity">

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="180dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@id/viewpager"
        android:background="#44000000"
        android:orientation="vertical"
        android:padding="5dp">

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:padding="3dp"
            android:text="Avengers Alliance 4"
            android:textColor="#ffffff" />

        <LinearLayout
            android:id="@+id/ll_point_group"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:orientation="horizontal">
            
        </LinearLayout>
    </LinearLayout>

</RelativeLayout>

Then modify the MainActivity code.

package com.itcast.test0429;

import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;

import butterknife.BindInt;
import butterknife.BindView;
import butterknife.ButterKnife;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    @BindView(R.id.viewpager)
    ViewPager viewpager;
    @BindView(R.id.tv_title)
    TextView tvTitle;
    @BindView(R.id.ll_point_group)
    LinearLayout llPointGroup;

    private ArrayList<ImageView> imageViews;

    //Image Resource ID
    private final int[] imageIds = {
            R.drawable.b1,
            R.drawable.b2,
            R.drawable.b3
    };

    //Picture Title Collection
    private final String[] imageDescriptions = {
            "Title I",
            "Title II",
            "Title III"
    };

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

        imageViews = new ArrayList<>();
        for(int i = 0;i < imageIds.length;i++){
            ImageView imageView = new ImageView(this);
            imageView.setBackgroundResource(imageIds[i]);

            //Add to the collection
            imageViews.add(imageView);
        }

        //Setting up the adapter
        viewpager.setAdapter(new MyAdapter());
    }

    class MyAdapter extends PagerAdapter{

        /**
         * Total number of pictures obtained
         * @return
         */
        @Override
        public int getCount() {
            return imageViews.size();
        }

        /**
         * Equivalent to getView method
         * @param container ViewPager Oneself
         * @param position  Location of the current instantiated page
         * @return
         */
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ImageView imageView = imageViews.get(position);
            container.addView(imageView);//Add to ViewPager
            Log.e(TAG,"instantiateItem==" + position + ",---imageView==" + imageView);
            return imageView;
        }

        /**
         * Compare view and object with the same instance
         * @param view  page
         * @param object    instantiateItem Method returns results
         * @return
         */
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        /**
         * Releasing resources
         * @param container ViewPager
         * @param position  Location to be released
         * @param object    Pages to be released
         */
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            Log.e(TAG,"destroyItem==" + position + ",---object==" + object);
            container.removeView((View) object);
        }
    }
}

Note the use of PageAdapter. I've commented on each method. You should understand it. ViewPager initializes two pages after running, up to three. As the number of pages increases, ViewPager automatically destroys the front pages and then provides them to the back pages for use. This is the content optimization of ViewPage. We can verify that I print logs in both initialization and destruction methods. . Now let's run the project.

Let's look at the log.

Only two initialization messages were printed, indicating that ViewPager initialized only two pages.
We slide ViewPager to the left to switch pages and watch the log information.

You will find that after creating three instances, the instance of the first page is destroyed, which confirms the conclusion just made.
This completes our first stage of coding. Next, we implement adding pointers and setting text according to page changes.
There are many ways to implement the pointer, which can be displayed by pictures or drawn by myself. I use the second way. Post the code for MainActivity.

package com.itcast.test0429;

import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.ArrayList;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnPageChange;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    @BindView(R.id.viewpager)
    ViewPager viewpager;
    @BindView(R.id.tv_title)
    TextView tvTitle;
    @BindView(R.id.ll_point_group)
    LinearLayout llPointGroup;

    private ArrayList<ImageView> imageViews;

    //Image Resource ID
    private final int[] imageIds = {
            R.drawable.b1,
            R.drawable.b2,
            R.drawable.b3,
            R.drawable.b1,
            R.drawable.b2,
            R.drawable.b3
    };

    /**
     * Last highlighted location
     */
    private int prePosition = 0;

    //Picture Title Collection
    private final String[] imageDescriptions = {
            "Title I",
            "Title II",
            "Title III",
            "Title IV",
            "Title V",
            "Title VI"
    };

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

        imageViews = new ArrayList<>();
        for (int i = 0; i < imageIds.length; i++) {
            ImageView imageView = new ImageView(this);
            imageView.setBackgroundResource(imageIds[i]);

            //Add to the collection
            imageViews.add(imageView);

            //Addition Points
            ImageView point = new ImageView(this);
            point.setBackgroundResource(R.drawable.point_selector);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(8, 8);

            if (i == 0) {
                point.setEnabled(true);//Show red
            } else {
                point.setEnabled(false);//Display grey
                params.leftMargin = 8;
            }

            point.setLayoutParams(params);

            llPointGroup.addView(point);
        }

        //Setting up the adapter
        viewpager.setAdapter(new MyAdapter());

        tvTitle.setText(imageDescriptions[prePosition]);

        //Setting up to listen for changes to ViewPager pages
        viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            /**
             * Callback this method when the page scrolls
             * @param position  Location of the current page
             * @param positionOffset    Percentage of sliding pages
             * @param positionOffsetPixels  Pixels sliding on the screen
             */
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            /**
             * Callback this method when a page is selected
             * @param position  Location of the selected page
             */
            @Override
            public void onPageSelected(int position) {
                //Setting text information for corresponding pages
                tvTitle.setText(imageDescriptions[position]);
                //Set the previous highlight to default - Grey
                llPointGroup.getChildAt(prePosition).setEnabled(false);
                //Currently set to highlight - red
                llPointGroup.getChildAt(position).setEnabled(true);

                prePosition = position;
            }

            /**
             * Callback this method when page scroll status changes
             * Still - "Sliding"
             * Sliding--Stillness
             * Quietness - "Dragging"
             * @param state
             */
            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }

    class MyAdapter extends PagerAdapter {

        /**
         * Total number of pictures obtained
         *
         * @return
         */
        @Override
        public int getCount() {
            return imageViews.size();
        }

        /**
         * Equivalent to getView method
         *
         * @param container ViewPager Oneself
         * @param position  Location of the current instantiated page
         * @return
         */
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ImageView imageView = imageViews.get(position);
            container.addView(imageView);//Add to ViewPager
            Log.e(TAG, "instantiateItem==" + position + ",---imageView==" + imageView);
            return imageView;
        }

        /**
         * Compare view and object with the same instance
         *
         * @param view   page
         * @param object instantiateItem Method returns results
         * @return
         */
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        /**
         * Releasing resources
         *
         * @param container ViewPager
         * @param position  Location to be released
         * @param object    Pages to be released
         */
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            Log.e(TAG, "destroyItem==" + position + ",---object==" + object);
            container.removeView((View) object);
        }
    }
}

The point_selecotr.xml file code is as follows.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="false" android:drawable="@drawable/point_normal"/>
    <item android:state_enabled="true" android:drawable="@drawable/point_press"/>
</selector>

The point_normal.xml file code is as follows.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <size
        android:width="8dp"
        android:height="8dp" />
    <solid android:color="#44000000" />
</shape>

The point_press.xml file code is as follows.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <size
        android:width="8dp"
        android:height="8dp" />
    <solid android:color="#ff0000" />
</shape>

We run the project and preview the results.

At this time, the content of the pointer and the text title changes with our sliding, so that our goal is achieved.
We achieve the third stage of demand, supporting infinite sliding left and right.
How can we achieve this requirement? The number of sliding pages is determined by the adapter's getCount method, so we return Integer.MAX_VALUE directly in the getCount method, which is the maximum value of int. This number is already very large, which can be said to be approximate to infinite sliding, but set such a large number, and our data is not so much. In order to avoid the problem of index crossing when sliding pages, we must model all the locations of pages and keep them within our limited data range, so that we can meet our needs. So let's modify MainActivity's code.

package com.itcast.test0429;

import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.ArrayList;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnPageChange;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    @BindView(R.id.viewpager)
    ViewPager viewpager;
    @BindView(R.id.tv_title)
    TextView tvTitle;
    @BindView(R.id.ll_point_group)
    LinearLayout llPointGroup;

    private ArrayList<ImageView> imageViews;

    //Image Resource ID
    private final int[] imageIds = {
            R.drawable.b1,
            R.drawable.b2,
            R.drawable.b3,
            R.drawable.b1,
            R.drawable.b2,
            R.drawable.b3
    };

    /**
     * Last highlighted location
     */
    private int prePosition = 0;

    //Picture Title Collection
    private final String[] imageDescriptions = {
            "Title I",
            "Title II",
            "Title III",
            "Title IV",
            "Title V",
            "Title VI"
    };

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

        imageViews = new ArrayList<>();
        for (int i = 0; i < imageIds.length; i++) {
            ImageView imageView = new ImageView(this);
            imageView.setBackgroundResource(imageIds[i]);

            //Add to the collection
            imageViews.add(imageView);

            //Addition Points
            ImageView point = new ImageView(this);
            point.setBackgroundResource(R.drawable.point_selector);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(8, 8);

            if (i == 0) {
                point.setEnabled(true);//Show red
            } else {
                point.setEnabled(false);//Display grey
                params.leftMargin = 8;
            }

            point.setLayoutParams(params);

            llPointGroup.addView(point);
        }

        //Setting up the adapter
        viewpager.setAdapter(new MyAdapter());

        tvTitle.setText(imageDescriptions[prePosition]);

        //Setting up to listen for changes to ViewPager pages
        viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            /**
             * Callback this method when the page scrolls
             * @param position  Location of the current page
             * @param positionOffset    Percentage of sliding pages
             * @param positionOffsetPixels  Pixels sliding on the screen
             */
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            /**
             * Callback this method when a page is selected
             * @param position  Location of the selected page
             */
            @Override
            public void onPageSelected(int position) {

                int realPosition = position % imageViews.size();

                //Setting text information for corresponding pages
                tvTitle.setText(imageDescriptions[realPosition]);
                //Set the previous highlight to default - Grey
                llPointGroup.getChildAt(prePosition).setEnabled(false);
                //Currently set to highlight - red
                llPointGroup.getChildAt(realPosition).setEnabled(true);

                prePosition = realPosition;
            }

            /**
             * Callback this method when page scroll status changes
             * Still - "Sliding"
             * Sliding--Stillness
             * Quietness - "Dragging"
             * @param state
             */
            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }

    class MyAdapter extends PagerAdapter {

        /**
         * Total number of pictures obtained
         *
         * @return
         */
        @Override
        public int getCount() {
            return Integer.MAX_VALUE;
        }

        /**
         * Equivalent to getView method
         *
         * @param container ViewPager Oneself
         * @param position  Location of the current instantiated page
         * @return
         */
        @Override
        public Object instantiateItem(ViewGroup container, int position) {

            int realPosition = position % imageViews.size();

            ImageView imageView = imageViews.get(realPosition);
            container.addView(imageView);//Add to ViewPager
            Log.e(TAG, "instantiateItem==" + position + ",---imageView==" + imageView);
            return imageView;
        }

        /**
         * Compare view and object with the same instance
         *
         * @param view   page
         * @param object instantiateItem Method returns results
         * @return
         */
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        /**
         * Releasing resources
         *
         * @param container ViewPager
         * @param position  Location to be released
         * @param object    Pages to be released
         */
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            Log.e(TAG, "destroyItem==" + position + ",---object==" + object);
            container.removeView((View) object);
        }
    }
}

That's all right. Let's run the project.

At first glance, it seems that there is no problem, but the program is bug gy, just so that we did not test it out, I will operate it again.

Did you find the problem? When I first entered the program, could the right slide not move? Since ViewPager defaults to start at 0, there are no other pages on the left, so you can't slide right. How can you solve this problem? Find out the reason for the problem, then there can be a solution, since there is no page on the left, then let it have pages, OK? If we position the first image in the middle, it will have a huge number of pages on the left and right. Although there are many pages, it is not endless. If there is a user, he is idle and panicky, he is desperately sliding. As a result, all the pages on the left or right are sliding out. In this case, we can only say that this person is really idle and panicky. In short, according to the normal situation, so many pages are enough for use. Household slides.
We know the principle, how to achieve it by coding? Simply, just add the following code after ViewPager sets up the adapter.

 //Set the middle position
int item = Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % imageViews.size();//Make sure that imageViews are integer multiples
viewpager.setCurrentItem(item);

Now run the project and preview the effect.

This will solve the problem. The whole case is over, which is only a small part of ViewPager's use. This program can also add many functions, such as automatic playback, click and jump, etc. Because of the limited space, I do not have to implement it, interested in writing by myself.
Source code has been uploaded to GitHub

Tags: Java Android ButterKnife xml encoding

Posted on Mon, 26 Aug 2019 01:02:49 -0400 by bigsexychris