Android实现用户引导界面
jopen
9年前
首先看效果图,盗了超级课程表几张图
在众多应用中,几乎每一款应用都有自己的Splash用户引导界面,该界面在用户首次启动展示,之后不会显示,主要向用户展示新功能.
分析
- 主要使用ViewPager+Indicator实现
- 主要是实现一个圆形指示器,这个圆形指示器继承LinearLayout,需要有一些属性可以自定义,比如指示器的颜色,大小,边距等
- 这个指示器也可以自动滚动,比如应用在幻灯片展示的地方
- 指示器是圆形的,需要我们自己绘制
- 这个圆形指示器实现了ViewPager.OnPageChangeListener接口
实现
- 定义自定义属性
属性的意思见名字就可以知道了
<resources> <declare-styleable name="CircleIndicator"> <attr name="circle_spacing" format="dimension"/> <attr name="circle_fill_color" format="color|reference"/> <attr name="circle_stroke_color" format="color|reference"/> <attr name="circle_radius" format="dimension"/> <attr name="circle_auto_scroll" format="boolean"/> <attr name="circle_scroll_delay_time" format="integer"/> <attr name="circle_scroll_animation" format="boolean"/> </declare-styleable> </resources>
定义自定义变量,从布局文件中解析进来,此外,如果布局文件没有使用,应该有一个默认的常量.
定义默认常量
private static final int DEFAULT_CIRCLE_SPACING = 5; private static final int DEFAULT_CIRCLE_COLOR=Color.WHITE; private static final int DEFAULT_CIRCLE_SIZE=3; private static final boolean DEFAULT_CIRCLE_AUTO_SCROLL=false; private static final int DEFAULT_CIRCLE_SCROLL_DELAY_TIME=3000; private static final boolean DEFAULT_CIRCLE_SCROLL_ANIMATION=true;
定义用于存储自定义属性的变量
private int mSpacing; private int mSize; private int mFillColor; private int mStrokeColor; private boolean mAutoScroll; private int mDelayTime; private boolean mIsAnimation;
定义其他辅助变量,比如Canvas,Bitmap,Paint等,用于绘制圆形指示器
private static final int CIRCLE_STROKE_WIDTH =1; private static final int BITMAP_PADDING =2; private ViewPager mViewPager; private int mCount; private int mLastIndex = 0; private Canvas mCanvas; private Paint mPaint; private Bitmap mSelectBitmap; private Bitmap mUnselectBitmap;
将自定义属性进行解析赋值给对应变量
private void initCustomParams(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleIndicator); try { mSpacing = typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_circle_spacing, DEFAULT_CIRCLE_SPACING); mFillColor=typedArray.getColor(R.styleable.CircleIndicator_circle_fill_color,DEFAULT_CIRCLE_COLOR); mStrokeColor=typedArray.getColor(R.styleable.CircleIndicator_circle_stroke_color,DEFAULT_CIRCLE_COLOR); mSize= typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_circle_radius, DEFAULT_CIRCLE_SIZE); mAutoScroll= typedArray.getBoolean(R.styleable.CircleIndicator_circle_auto_scroll, DEFAULT_CIRCLE_AUTO_SCROLL); mDelayTime=typedArray.getInt(R.styleable.CircleIndicator_circle_scroll_delay_time,DEFAULT_CIRCLE_SCROLL_DELAY_TIME); mIsAnimation=typedArray.getBoolean(R.styleable.CircleIndicator_circle_scroll_animation,DEFAULT_CIRCLE_SCROLL_ANIMATION); } finally { typedArray.recycle(); } }
我们的指示器是自己绘制出来的,接下来绘制圆形指示器
private void init() { setOrientation(HORIZONTAL); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setDither(true); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); mPaint.setStrokeWidth(dip2px(CIRCLE_STROKE_WIDTH)); mPaint.setColor(mFillColor); int size=dip2px(mSize+ BITMAP_PADDING + BITMAP_PADDING); int radius=dip2px(mSize / 2); int centerPoint=radius+ BITMAP_PADDING; mSelectBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); mUnselectBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(); mCanvas.setBitmap(mSelectBitmap); mCanvas.drawCircle(centerPoint, centerPoint, radius, mPaint); mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(mStrokeColor); mCanvas.setBitmap(mUnselectBitmap); mCanvas.drawCircle(centerPoint, centerPoint, radius, mPaint); }
实现构造方法,最终调用三个参数的构造方法,并调用相关函数进行初始化
public CircleIndicator(Context context) { this(context, null); } public CircleIndicator(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CircleIndicator(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initCustomParams(context, attrs); init(); }
实现指示器相关逻辑
- 首先需要初始化指示器的位置,应该是ViewPager的第一页,即初始化位置为0,调用initIndicator,并设置指示器的背景图为选中状态.记录上次指示器的位置即当前位置.
- removeIndicator移出指示器只要移出当前类的所有子View即可
- updateIndicator需要将上次的位置背景图设置为未选中,当前位置设置未选中,并记录上次位置为当前位置
- addIndicator需要将圆形指示器的数目传入,其值为ViewPager的页数,并新建ImageView设置背景图为未选中的时候的图,并设置外边距,将其添加到当前类的子View中,如果设置了自动滚动,还需要进行自动滚动
- setViewPager函数进行一些初始化操作
public void setViewPager(ViewPager viewPager) { mViewPager = viewPager; mViewPager.addOnPageChangeListener(this); if (mViewPager != null) { mCount = mViewPager.getAdapter().getCount(); addIndicator(mCount); } } private void addIndicator(int count) { removeIndicator(); if (count <= 0) return; for (int i = 0; i < count; i++) { ImageView imageView = new ImageView(getContext()); LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.leftMargin = mSpacing/2; params.rightMargin = mSpacing/2; imageView.setImageBitmap(mUnselectBitmap); addView(imageView, params); } initIndicator(); if(mAutoScroll){ sendScrollMessage(mDelayTime); } } private void initIndicator() { ((ImageView) getChildAt(0)).setImageBitmap(mSelectBitmap); mLastIndex=0; } private void removeIndicator() { removeAllViews(); } private void updateIndicator(int position) { if (position != mLastIndex) { ((ImageView) getChildAt(mLastIndex)).setImageBitmap(mUnselectBitmap); ((ImageView) getChildAt(position)).setImageBitmap(mSelectBitmap); } mLastIndex = position; }
实现自动滚动,主要通过Handler进行延时实现
private Handler mHandler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case SCROLL_WHAT: scrollOnce(); sendScrollMessage(mDelayTime); break; } } }; public void scrollOnce() { PagerAdapter adapter = mViewPager.getAdapter(); if (adapter == null) { return; } int nextIndex=mViewPager.getCurrentItem(); ++nextIndex; if(nextIndex >=mCount){ nextIndex =0; } updateIndicator(nextIndex); mViewPager.setCurrentItem(nextIndex, mIsAnimation); } private void sendScrollMessage(long delayTimeInMills) { mHandler.removeMessages(SCROLL_WHAT); mHandler.sendEmptyMessageDelayed(SCROLL_WHAT, delayTimeInMills); }
实现相关getter和setter函数
private void setAutoScroll(boolean autoScroll){ if (autoScroll){ sendScrollMessage(mDelayTime); }else{ mHandler.removeMessages(SCROLL_WHAT); } mAutoScroll=autoScroll; } public boolean isAutoScroll() { return mAutoScroll; } public int getDelayTime() { return mDelayTime; } public void setDelayTime(int delayTime) { mDelayTime = delayTime; } public boolean isAnimation() { return mIsAnimation; } public void setIsAnimation(boolean isAnimation) { mIsAnimation = isAnimation; }
实现接口相关函数
@Override public void onPageScrolled(int i, float v, int i1) { } @Override public void onPageSelected(int position) { updateIndicator(position); } @Override public void onPageScrollStateChanged(int i) { }
以及一个单位转换的工具函数
private int dip2px(int dip) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics()); }
使用
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:indicator="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=".MainActivity"> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentTop="true" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" ></android.support.v4.view.ViewPager> <cn.edu.zafu.view.CircleIndicator android:id="@+id/circle_indicator" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:layout_alignParentBottom="true" android:layout_marginBottom="10dp" indicator:circle_spacing="5dp" indicator:circle_radius="3dp" indicator:circle_fill_color="#728bff" indicator:circle_stroke_color="#aaa" indicator:circle_auto_scroll="true" > </cn.edu.zafu.view.CircleIndicator> </RelativeLayout>
private void initView() { mViewPager= (ViewPager) findViewById(R.id.viewpager); mCircleIndicator= (CircleIndicator) findViewById(R.id.circle_indicator); mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) { private int[] resId={R.mipmap.ic_help_view_1,R.mipmap.ic_help_view_2,R.mipmap.ic_help_view_3,R.mipmap.ic_help_view_4}; private Map<Integer,Fragment> mFragments=new HashMap<Integer,Fragment>(); @Override public Fragment getItem(int i) { Fragment fragment=mFragments.get(i); if(fragment==null){ fragment=BlankFragment.newInstance(resId[i],i,resId.length); mFragments.put(i,fragment); } return fragment; } @Override public int getCount() { return resId.length; } }); mCircleIndicator.setViewPager(mViewPager); }
package cn.edu.zafu.splash; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.RelativeLayout; public class BlankFragment extends Fragment { private static final String IMAGE_ID = "imageId"; private static final String CUCRNT = "curcent"; private static final String TOTAL = "total"; private int mImageId; private int mCurcent; private int mTotal; public static BlankFragment newInstance(int imageId,int current,int total) { BlankFragment fragment = new BlankFragment(); Bundle args = new Bundle(); args.putInt(IMAGE_ID, imageId); args.putInt(CUCRNT, current); args.putInt(TOTAL, total); fragment.setArguments(args); return fragment; } public BlankFragment() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mImageId = getArguments().getInt(IMAGE_ID); mCurcent = getArguments().getInt(CUCRNT); mTotal = getArguments().getInt(TOTAL); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view= inflater.inflate(R.layout.fragment_blank, container, false); return view; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); super.onViewCreated(view, savedInstanceState); ImageView imageView= (ImageView) view.findViewById(R.id.image); imageView.setImageResource(mImageId); if(mCurcent==mTotal-1){ RelativeLayout relativeLayout= (RelativeLayout) view.findViewById(R.id.relativelayout); ImageButton button=new ImageButton(getActivity().getApplicationContext()); button.setBackgroundResource(R.drawable.last_button_selector); RelativeLayout.LayoutParams params=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,RelativeLayout.LayoutParams.WRAP_CONTENT); params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); params.addRule(RelativeLayout.CENTER_HORIZONTAL); params.bottomMargin=dip2px(80); relativeLayout.addView(button,params); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int versionCode=Util.getAppVersionCode(getActivity()); Util.set(getActivity(),Util.FILE_NAME,versionCode+"",true); Intent intent=new Intent(getActivity(),SecondActivity.class); startActivity(intent); getActivity().finish(); } }); } } private int dip2px(int dip) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getActivity().getResources().getDisplayMetrics()); } }
如果要实现是否首次启动,如果是才显示的话需要加一些逻辑判断,如果当前版本号已经持久化了,则直接跳过,这个数据是在Splash页面最后一个按钮点击事件里处理的
private boolean ignoreSplash() { if(Util.contatins(this, Util.FILE_NAME, Util.getAppVersionCode(this) + "")){ Intent intent=new Intent(MainActivity.this,SecondActivity.class); startActivity(intent); this.finish(); return true; } return false; }
public void onClick(View v) { int versionCode=Util.getAppVersionCode(getActivity()); Util.set(getActivity(),Util.FILE_NAME,versionCode+"",true); Intent intent=new Intent(getActivity(),SecondActivity.class); startActivity(intent); getActivity().finish(); }