android 打造炫酷导航栏(仿UC头条)

Den39A 5年前

来自: http://blog.csdn.net/qq_16064871/article/details/50682388


年后开始上班甚是清闲,所以想捣鼓一些东西。在翻阅大神杰作Android 教你打造炫酷的ViewPagerIndicator 不仅仅是高仿MIUI 的时候看到下面有一条评论说,如果导航栏能滑动就更好了。我就想我可以去改一下就可以。然后又想感觉有点像UC的头条的界面。于是就往里面加东西,调试写代码。弄了两天有点效果了,写出来看看了。

项目下载地址:http://download.csdn.net/detail/qq_16064871/9434291

1、先看效果

用咔咔大师录屏的gif效果,有点失真。



这是第一个版本的效果。因为我不断往里面加东西,所以有几个版本了。

2、这个版本基本代码

实现思路就是自定义绘制了。主要有两层,第一层是ViewPagerIndicator。主要负责导航栏的三角形指示器的绘制,以及页面滑动的回调,控制。当然这里需要android.support.v4.view.ViewPager这东西配合使用。

第二层是,导航栏的滑动效果,以及最左、最右有 反弹的效果。这个效果我是从以前一篇博文改动到了这里来。链接:android 滚动条下拉反弹的效果(类似微信朋友圈)。这里效果是垂直,改为横向就行了。还有这两层同时使用需要处理就是滑动不要冲突就可以了。

xml布局代码:

<com.ucnew.view.BounceScrollView              android:id="@+id/id_scrollview"              android:layout_width="0dp"              android:layout_height="45dp"              android:layout_weight="1"              android:focusableInTouchMode="false"              android:scrollbars="none" >                <com.ucnew.view.ViewPagerIndicator                  android:id="@+id/id_indicator"                  android:layout_width="match_parent"                  android:layout_height="45dp"                  android:orientation="horizontal"                  mmsx:item_count="4" >              </com.ucnew.view.ViewPagerIndicator>          </com.ucnew.view.BounceScrollView>

3、自定义view的java代码

ViewPagerIndicator

package com.ucnew.view;    import java.util.List;    import com.ucnew.activity.R;    import android.content.Context;  import android.content.res.TypedArray;  import android.graphics.Canvas;  import android.graphics.Color;  import android.graphics.CornerPathEffect;  import android.graphics.Paint;  import android.graphics.Paint.Style;  import android.graphics.Path;  import android.support.v4.view.ViewPager;  import android.support.v4.view.ViewPager.OnPageChangeListener;  import android.util.AttributeSet;  import android.util.DisplayMetrics;  import android.util.TypedValue;  import android.view.Gravity;  import android.view.View;  import android.view.WindowManager;  import android.widget.LinearLayout;  import android.widget.TextView;    public class ViewPagerIndicator extends LinearLayout  {   //绘制三角形的画笔   private Paint mPaint;    //path构成一个三角形   private Path mPath;   //三角形的宽度   private int mTriangleWidth;   // 三角形的高度   private int mTriangleHeight;    //三角形的宽度为单个Tab的1/6   private static final float RADIO_TRIANGEL = 1.0f / 6;   // 三角形的最大宽度   private final int DIMENSION_TRIANGEL_WIDTH = (int) (getScreenWidth() / 3 * RADIO_TRIANGEL);   //初始时,三角形指示器的偏移量   private int mInitTranslationX;    // 手指滑动时的偏移量   private float mTranslationX;    // tab数量   private int mTabVisibleCount;    // tab上的内容   private List<String> mTabTitles;    // 与之绑定的ViewPager   public ViewPager mViewPager;    // 标题正常时的颜色   private static final int COLOR_TEXT_NORMAL = 0x77FFFFFF;    //标题选中时的颜色   private static final int COLOR_TEXT_HIGHLIGHTCOLOR = 0xFFFFFFFF;      public ViewPagerIndicator(Context context)   {    this(context, null);   }     public ViewPagerIndicator(Context context, AttributeSet attrs)   {    super(context, attrs);      // 获得自定义属性,tab的数量    TypedArray a = context.obtainStyledAttributes(attrs,      R.styleable.ViewPagerIndicator);    mTabVisibleCount = a.getInt(R.styleable.ViewPagerIndicator_item_count,4);    if (mTabVisibleCount < 0)     mTabVisibleCount = 4;    a.recycle();      // 初始化画笔    mPaint = new Paint();    mPaint.setAntiAlias(true);    mPaint.setColor(Color.parseColor("#ffffffff"));    mPaint.setStyle(Style.FILL);    mPaint.setPathEffect(new CornerPathEffect(3));     }     //绘制指示器   @Override   protected void dispatchDraw(Canvas canvas)   {    canvas.save();    // 画笔平移到正确的位置    canvas.translate(mInitTranslationX + mTranslationX, getHeight() + 1);    canvas.drawPath(mPath, mPaint);    canvas.restore();      super.dispatchDraw(canvas);   }      //初始化三角形的宽度   @Override   protected void onSizeChanged(int w, int h, int oldw, int oldh)   {    super.onSizeChanged(w, h, oldw, oldh);    mTriangleWidth = (int) (w / mTabVisibleCount * RADIO_TRIANGEL);// 1/6 of                    // width    mTriangleWidth = Math.min(DIMENSION_TRIANGEL_WIDTH, mTriangleWidth);      // 初始化三角形    initTriangle();      // 初始时的偏移量    mInitTranslationX = getScreenWidth() / mTabVisibleCount / 2 - mTriangleWidth / 2;   }      //设置可见的tab的数量   public void setVisibleTabCount(int count)   {    this.mTabVisibleCount = count;   }        //设置tab的标题内容 可选,生成textview加入布局,灵活处理   public void setTabItemTitles(List<String> datas)   {    // 如果传入的list有值,则移除布局文件中设置的view    if (datas != null && datas.size() > 0)    {     this.removeAllViews();     this.mTabTitles = datas;       for (String title : mTabTitles)     {      // 添加view      addView(generateTextView(title));     }     // 设置item的click事件     setItemClickEvent();    }     }      //根据标题生成我们的TextView   private TextView generateTextView(String text)   {    TextView tv = new TextView(getContext());    LinearLayout.LayoutParams lp = new LayoutParams(      LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);    lp.width = getScreenWidth() / mTabVisibleCount;    tv.setGravity(Gravity.CENTER);    tv.setTextColor(COLOR_TEXT_NORMAL);    tv.setText(text);    tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);    tv.setLayoutParams(lp);    return tv;   }     //对外的ViewPager的回调接口   public interface PageChangeListener   {    public void onPageScrolled(int position, float positionOffset,      int positionOffsetPixels);      public void onPageSelected(int position);      public void onPageScrollStateChanged(int state);   }     // 对外的ViewPager的回调接口   private PageChangeListener onPageChangeListener;     // 对外的ViewPager的回调接口的设置   public void setOnPageChangeListener(PageChangeListener pageChangeListener)   {    this.onPageChangeListener = pageChangeListener;   }     // 设置关联的ViewPager,以及传入 BounceScrollView,进行设置滚动   public void setViewPager(ViewPager mViewPager, final BounceScrollView scrollView, int pos)   {    this.mViewPager = mViewPager;        mViewPager.setOnPageChangeListener(new OnPageChangeListener()    {     @Override     public void onPageSelected(int position)     {      // 设置字体颜色高亮      resetTextViewColor();      highLightTextView(position);        // 回调      if (onPageChangeListener != null)      {       onPageChangeListener.onPageSelected(position);      }     }       @Override     public void onPageScrolled(int position, float positionOffset,       int positionOffsetPixels)     {      // 滚动      scroll(scrollView,position, positionOffset);      // 回调      if (onPageChangeListener != null)      {       onPageChangeListener.onPageScrolled(position,         positionOffset, positionOffsetPixels);      }     }       @Override     public void onPageScrollStateChanged(int state)     {      // 回调      if (onPageChangeListener != null)      {       onPageChangeListener.onPageScrollStateChanged(state);      }       }    });    // 设置当前页    mViewPager.setCurrentItem(pos);    // 高亮    highLightTextView(pos);   }     //高亮文本   protected void highLightTextView(int position)   {    View view = getChildAt(position);    if (view instanceof TextView)    {     ((TextView) view).setTextColor(COLOR_TEXT_HIGHLIGHTCOLOR);    }     }     //重置文本颜色   private void resetTextViewColor()   {    for (int i = 0; i < getChildCount(); i++)    {     View view = getChildAt(i);     if (view instanceof TextView)     {      ((TextView) view).setTextColor(COLOR_TEXT_NORMAL);     }    }   }     // 设置点击事件   public void setItemClickEvent()   {    int cCount = getChildCount();    for (int i = 0; i < cCount; i++)    {     final int j = i;     View view = getChildAt(i);     view.setOnClickListener(new OnClickListener()     {      @Override      public void onClick(View v)      {       mViewPager.setCurrentItem(j);      }     });    }   }     // 初始化三角形指示器   private void initTriangle()   {    mPath = new Path();      mTriangleHeight = (int) (mTriangleWidth / 2 / Math.sqrt(2));    mPath.moveTo(0, 0);    mPath.lineTo(mTriangleWidth, 0);    mPath.lineTo(mTriangleWidth / 2, -mTriangleHeight);    mPath.close();   }      //指示器跟随手指滚动,以及容器滚动   public void scroll(BounceScrollView scrollView,int position, float offset)   {    // 不断改变偏移量,invalidate    mTranslationX = getScreenWidth() / mTabVisibleCount * (position + offset);      int tabWidth = getScreenWidth() / mTabVisibleCount;      // 容器滚动,当移动到倒数最后一个的时候,开始滚动    if (offset > 0 && position >= (mTabVisibleCount - 1) && getChildCount() > mTabVisibleCount)    {          if (mTabVisibleCount != 1)     {      //下面注释掉,是滚动ViewPagerIndicator这个LinearLayout  //    this.scrollTo((position - (mTabVisibleCount - 1)) * tabWidth + (int) (tabWidth * offset), 0);      //只滚动滚动条,禁止滚动lineayout      scrollView.setScrolledTo((position - (mTabVisibleCount - 1)) * tabWidth + (int) (tabWidth * offset), 0);     } else     // 为count为1时 的特殊处理     {      this.scrollTo(position * tabWidth + (int) (tabWidth * offset), 0);  //    scrollView.setScrolledTo(position * tabWidth + (int) (tabWidth * offset), 0);     }    }    //处理特殊情况    else if (offset > 0 && position <= mTabVisibleCount - 1) {     scrollView.setScrolledTo(0, 0);    }      invalidate();   }     //设置布局中view的一些必要属性;如果设置了setTabTitles,布局中view则无效   @Override   protected void onFinishInflate()   {    super.onFinishInflate();      int cCount = getChildCount();      if (cCount == 0)     return;      for (int i = 0; i < cCount; i++)    {     View view = getChildAt(i);     LinearLayout.LayoutParams lp = (LayoutParams) view       .getLayoutParams();     lp.weight = 0;     lp.width = getScreenWidth() / mTabVisibleCount;     view.setLayoutParams(lp);    }    // 设置点击事件    setItemClickEvent();     }     //获得屏幕的宽度   public int getScreenWidth()   {    WindowManager wm = (WindowManager) getContext().getSystemService(      Context.WINDOW_SERVICE);    DisplayMetrics outMetrics = new DisplayMetrics();    wm.getDefaultDisplay().getMetrics(outMetrics);    return outMetrics.widthPixels ;   }    }

BounceScrollView

package com.ucnew.view;    import android.content.Context;  import android.graphics.Rect;  import android.util.AttributeSet;  import android.view.MotionEvent;  import android.view.View;  import android.view.animation.TranslateAnimation;  import android.widget.HorizontalScrollView;    /**   * ScrollView反弹效果的实现   */  public class BounceScrollView extends HorizontalScrollView {   // 孩子View   private View inner;      // 点击时x坐标   private float x;   // 矩形(这里只是个形式,只是用于判断是否需要动画   private Rect normal = new Rect();      // 是否开始计算   private boolean isCount = false;   private RotatImageView  mRotatImageView;      public BounceScrollView(Context context, AttributeSet attrs) {    super(context, attrs);   }      /***    * 根据 XML 生成视图工作完成.该函数在生成视图的最后调用,在所有子视图添加完之后. 即使子类覆盖了 onFinishInflate    * 方法,也应该调用父类的方法,使该方法得以执行.    */   @Override   protected void onFinishInflate() {    if (getChildCount() > 0) {     inner = getChildAt(0);    }   }      //手动需要设置滚动位置   public void setScrolledTo(int position, float positionOffset) {    this.scrollTo(position,(int) positionOffset);   }      //监听touch   @Override   public boolean onTouchEvent(MotionEvent ev) {    if (inner != null) {     commOnTouchEvent(ev);    }      return super.onTouchEvent(ev);   }     //触摸事件   public void commOnTouchEvent(MotionEvent ev) {    int action = ev.getAction();    switch (action) {    case MotionEvent.ACTION_DOWN:     break;    case MotionEvent.ACTION_UP:     // 手指松开.     if (isNeedAnimation()) {      animation();      isCount = false;     }     break;    /***     * 排除出第一次移动计算,因为第一次无法得知y坐标, 在MotionEvent.ACTION_DOWN中获取不到,     * 因为此时是MyScrollView的touch事件传递到到了LIstView的孩子item上面.所以从第二次计算开始.     * 然而我们也要进行初始化,就是第一次移动的时候让滑动距离归0. 之后记录准确了就正常执行.     */    case MotionEvent.ACTION_MOVE:     final float preX = x;// 按下时的y坐标     float nowX = ev.getX();// 时时y坐标     int deltaX = (int) (preX - nowX);// 滑动距离     if (!isCount) {      deltaX = 0; // 在这里要归0.     }       x = nowX;     // 当滚动到最上或者最下时就不会再滚动,这时移动布局     if (isNeedMove()) {      // 初始化头部矩形      if (normal.isEmpty()) {       // 保存正常的布局位置       normal.set(inner.getLeft(), inner.getTop(),         inner.getRight(), inner.getBottom());      }      // 移动布局      inner.layout(inner.getLeft() - deltaX / 4, inner.getTop(),        inner.getRight()  - deltaX / 4, inner.getBottom());            //图片加号旋转,如果不需要这个直接删了就行      if (mRotatImageView != null) {       mRotatImageView.setRotationLeft();      }     }     isCount = true;     break;      default:     break;    }   }     //回缩动画   public void animation() {    // 开启移动动画    TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(),      normal.top);    ta.setDuration(200);    inner.startAnimation(ta);    // 设置回到正常的布局位置    inner.layout(normal.left, normal.top, normal.right, normal.bottom);    normal.setEmpty();     }      //设置图片加号旋转   public void setRotatImageView(RotatImageView rotatImageView){    this.mRotatImageView = rotatImageView;   }     // 是否需要开启动画   public boolean isNeedAnimation() {    return !normal.isEmpty();   }     /***    * 是否需要移动布局 inner.getMeasuredHeight():获取的是控件的总高度    * getHeight():获取的是屏幕的高度    */   public boolean isNeedMove() {    int offset = inner.getMeasuredWidth() - getWidth();    int scrollX = getScrollX();        // 0是顶部反弹    //是底部反弹加上        if (scrollX == 0  || scrollX == offset) {     return true;    }    return false;   }    }

都有很详细的注释,以及测试需要说明。

那么就直接看下一个版本。

4、看下一个版本效果


多了一个图标,这个图标可以用来监听,等等。

我后面捣鼓加了一个旋转动画,动画效果可能不是很好。但也可以看看。

5、xml代码

有一些自定义属性以及效果的代码就不贴,感兴趣下载源码。看不懂的,看我前几篇文章,自定义控件使用。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:tools="http://schemas.android.com/tools"      xmlns:mmsx="http://schemas.android.com/apk/res/com.ucnew.activity"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:background="#ffffffff"      android:orientation="vertical" >        <LinearLayout          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:background="#8000bf5f" >            <com.ucnew.view.BounceScrollView              android:id="@+id/id_scrollview"              android:layout_width="0dp"              android:layout_height="45dp"              android:layout_weight="1"              android:focusableInTouchMode="false"              android:scrollbars="none" >                <com.ucnew.view.ViewPagerIndicator                  android:id="@+id/id_indicator"                  android:layout_width="match_parent"                  android:layout_height="45dp"                  android:orientation="horizontal"                  mmsx:item_count="4" >              </com.ucnew.view.ViewPagerIndicator>          </com.ucnew.view.BounceScrollView>            <com.ucnew.view.RotatImageView              android:id="@+id/id_rotat_imageView"              android:layout_width="50dp"              android:layout_height="wrap_content"              android:paddingLeft="30dp"              android:layout_gravity="center"              mmsx:src="@drawable/add" >          </com.ucnew.view.RotatImageView>      </LinearLayout>        <android.support.v4.view.ViewPager          android:id="@+id/id_vp"          android:layout_width="match_parent"          android:layout_height="0dp"          android:layout_weight="1" >      </android.support.v4.view.ViewPager>    </LinearLayout>

6、增加以及这里主要主要需要改的代码。

ViewPagerIndicator类的这个函数

//获得屏幕的宽度   public int getScreenWidth()   {    WindowManager wm = (WindowManager) getContext().getSystemService(      Context.WINDOW_SERVICE);    DisplayMetrics outMetrics = new DisplayMetrics();    wm.getDefaultDisplay().getMetrics(outMetrics);    //获取是整个屏幕的宽度,我试过自定义宽度测量宽度,不行。因为本身还没内容,是后面添加的。所以需要后面加东西    //需要这里减去宽度就行。其中这里减去120就是60dp的宽度    return outMetrics.widthPixels -100;   }

还有一个图片的旋转类RotatImageView

package com.ucnew.view;    import com.ucnew.activity.R;    import android.content.Context;  import android.content.res.TypedArray;  import android.graphics.Bitmap;  import android.graphics.BitmapFactory;  import android.graphics.Canvas;  import android.graphics.Matrix;  import android.graphics.Paint;  import android.graphics.PaintFlagsDrawFilter;  import android.util.AttributeSet;  import android.util.Log;  import android.view.View;    public class RotatImageView extends View {   private Paint paint = null; // 画笔   private Bitmap mbitmap = null; // 图片位图   private Bitmap bitmapDisplay = null;   private Matrix matrix = null;   private int mWidth = 0; // 图片的宽度   private int mHeight = 0; // 图片的高度   private float fAngle = 180.0f; // 图片旋转   private PaintFlagsDrawFilter mDrawFilter;      public RotatImageView(Context context) {    super(context);   }      public RotatImageView(Context context, AttributeSet attrs)   {    this(context, attrs, 0);   }     /**    * 初始化一些自定义的参数    *     * @param context    * @param attrs    * @param defStyle    */   public RotatImageView(Context context, AttributeSet attrs, int defStyle) {    super(context, attrs, defStyle);      TypedArray a = context.getTheme().obtainStyledAttributes(attrs,      R.styleable.RotatImageView, defStyle, 0);      int n = a.getIndexCount();    for (int i = 0; i < n; i++) {     int attr = a.getIndex(i);     switch (attr) {     // 原始图片,在布局里面获取     case R.styleable.RotatImageView_src:      mbitmap = BitmapFactory.decodeResource(getResources(),a.getResourceId(attr, 0));      bitmapDisplay = mbitmap;      break;     }    }    a.recycle();            mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);    paint = new Paint();    paint.setFlags(Paint.ANTI_ALIAS_FLAG);      matrix = new Matrix();   }      /**    * 计算控件的高度和宽度    */   @Override   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)   {    // 设置宽度    int specMode = MeasureSpec.getMode(widthMeasureSpec);    int specSize = MeasureSpec.getSize(widthMeasureSpec);      //match_parent或者设置的精确值获取    //MeasureSpec.EXACTLY    if (specMode == MeasureSpec.EXACTLY)    {     mWidth = specSize;    }     else    {     // 由图片决定的宽     //getPaddingLeft(),getPaddingRight()这两个值是控件属性的向内偏移的距离值,所以的一起计算     //区别于layout_marginLeft,两个控件的左间距值设置     int desireByImg = getPaddingLeft() + getPaddingRight()       + mbitmap.getWidth();          // wrap_content     if (specMode == MeasureSpec.AT_MOST)     {      //所以最小的值,宽度的话是左右内偏移距离之和      mWidth = Math.min(desireByImg, specSize);     } else        mWidth = desireByImg;    }      // 设置高度,部分解释同上    specMode = MeasureSpec.getMode(heightMeasureSpec);    specSize = MeasureSpec.getSize(heightMeasureSpec);        //match_parent或者设置的精确值获取    //MeasureSpec.EXACTLY    if (specMode == MeasureSpec.EXACTLY)    {     mHeight = specSize;    } else    {     int desire = getPaddingTop() + getPaddingBottom()       + mbitmap.getHeight();       // wrap_content     if (specMode == MeasureSpec.AT_MOST)     {      mHeight = Math.min(desire, specSize);     } else      mHeight = desire;    }      //计算好的宽度以及高度是值,设置进去    setMeasuredDimension(mWidth, mHeight);   }     // 向左旋转   public void setRotationLeft() {    fAngle = fAngle - 20;    setAngle();   }     // 向右旋转   public void setRotationRight() {    fAngle = fAngle + 20;    setAngle();   }      private boolean isRoate = false;   // 设置旋转比例   private void setAngle() {    Log.i("Show", String.valueOf(fAngle));    isRoate = true;    //设置图片的旋转中心,即绕(X,Y)这点进行中心旋转 要旋转的角度    matrix.preRotate(fAngle, (float)mbitmap.getWidth()/2, (float)mbitmap.getHeight()/2);     invalidate();   }     @Override   protected void onDraw(Canvas canvas) {    super.onDraw(canvas);        //消除锯齿, 图片旋转后的锯齿消除不成功,实在不行图片边缘加一些白色像素点    canvas.setDrawFilter(mDrawFilter);     if (isRoate) {     canvas.drawBitmap(bitmapDisplay, matrix, paint);     isRoate = false;    }else {     canvas.drawBitmap(bitmapDisplay, 0, 0, paint);     }   }  }

activity调用以及初始化这些自定义控件

package com.ucnew.activity;    import java.util.ArrayList;  import java.util.Arrays;  import java.util.List;    import com.ucnew.activity.R;  import com.ucnew.fragment.VpSimpleFragment;  import com.ucnew.view.BounceScrollView;  import com.ucnew.view.RotatImageView;  import com.ucnew.view.ViewPagerIndicator;    import android.os.Bundle;  import android.support.v4.app.Fragment;  import android.support.v4.app.FragmentActivity;  import android.support.v4.app.FragmentPagerAdapter;  import android.support.v4.view.ViewPager;  import android.view.Window;    public class MainActivity extends FragmentActivity  {   private List<Fragment> mTabContents = new ArrayList<Fragment>();   private FragmentPagerAdapter mAdapter;   private ViewPager mViewPager;   private List<String> mDatas = Arrays.asList("页面1", "页面2", "页面3", "页面4",     "页面5", "页面6", "页面7", "页面8", "页面9");     private ViewPagerIndicator mIndicator;   private BounceScrollView mScrollView;   private RotatImageView mRotatImageView;     @Override   protected void onCreate(Bundle savedInstanceState)   {    super.onCreate(savedInstanceState);    requestWindowFeature(Window.FEATURE_NO_TITLE);    setContentView(R.layout.vp_indicator);      initView();    initDatas();    //设置Tab上的标题    mIndicator.setTabItemTitles(mDatas);    mViewPager.setAdapter(mAdapter);    //设置关联的ViewPager    mIndicator.setViewPager(mViewPager,mScrollView,0);    //设置关联的图片旋转,根据需要设置,效果不是很好    mScrollView.setRotatImageView(mRotatImageView);   }     private void initDatas()   {      for (String data : mDatas)    {     VpSimpleFragment fragment = VpSimpleFragment.newInstance(data);     mTabContents.add(fragment);    }      mAdapter = new FragmentPagerAdapter(getSupportFragmentManager())    {     @Override     public int getCount()     {      return mTabContents.size();     }       @Override     public Fragment getItem(int position)     {      return mTabContents.get(position);     }    };   }     private void initView()   {    mScrollView = (BounceScrollView) findViewById(R.id.id_scrollview);    mViewPager = (ViewPager) findViewById(R.id.id_vp);    mIndicator = (ViewPagerIndicator) findViewById(R.id.id_indicator);    mRotatImageView = (RotatImageView)findViewById(R.id.id_rotat_imageView);       }      }

还有部分代码没贴。自已下载查看吧。


7、最后效果图


录屏不是很清晰,来一张截图,够清晰的



项目下载地址:http://download.csdn.net/detail/qq_16064871/9434291

用到知识

Android 教你打造炫酷的ViewPagerIndicator 不仅仅是高仿MIUI

android 滚动条下拉反弹的效果(类似微信朋友圈)

 

android 自定义控件以及自定义view学习(随机验证码生成)


 

android 自定义控件属性(TypedArray以及attrs解释)


 

android 自定义图片合集(自定义控件)