功能要求是屏幕上固定显示 3 个 Layout 项(图片+文字),支持点击切换到选择的 Layout 项,并支持滑动切换到最近的 Layout 项。


最后的效果如下:


下面逐步上代码:

布局文件 activity_main.xml 如下:

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"android:paddingBottom="@dimen/activity_vertical_margin"tools:context=".MainActivity"><TextViewandroid:text=""android:layout_width="wrap_content"android:layout_height="wrap_content"/><HorizontalScrollViewandroid:id="@+id/hsv"android:layout_width="match_parent"android:layout_height="wrap_content"android:scrollbarStyle="outsideInset"><cn.steven.hsvp_w_picpathswitch.HSVLayoutandroid:id="@+id/avatar_layout"android:layout_width="wrap_content"android:layout_height="wrap_content"/></HorizontalScrollView><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@id/hsv"android:layout_marginTop="12dp"android:id="@+id/scrollx_tv"/><Buttonandroid:onClick="onClickScrollX"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/scrollx_tv"android:layout_marginTop="12dp"android:text="滚动位置"/></RelativeLayout>

上面的 HorizontalScrollView 中使用了自定义的 HSVLayout 布局,定义(HSVLayout.java)如下:

publicclassHSVLayoutextendsLinearLayout{privateHSVAdapteradapter;privateContextcontext;publicHSVLayout(Contextcontext,AttributeSetattrs){super(context,attrs);this.context=context;}/***设置布局适配器**@paramlayoutWidthPerAvatar指定了每一个item的占用宽度*@paramadapter适配器*@paramnotify在点击某一个item后的回调*/publicvoidsetAdapter(intlayoutWidthPerAvatar,HSVAdapteradapter,finalINotifySelectItemnotify){this.adapter=adapter;for(inti=0;i<adapter.getCount();i++){finalMap<String,Object>map=adapter.getItem(i);Viewview=adapter.getView(i,null,null);//为视图设定点击监听器finalintfinalI=i;view.setOnClickListener(newOnClickListener(){@OverridepublicvoidonClick(Viewv){//点击选择了某一个Item视图notify.select(finalI);}});this.setOrientation(HORIZONTAL);//设置固定显示的每个item布局的宽度this.addView(view,newLinearLayout.LayoutParams(layoutWidthPerAvatar,LayoutParams.WRAP_CONTENT));}}}

HSVLayout 中的每一个 视图 item 都是由 HSVAdapter 进行设置的,这个比较简单,只控制了每一个 item 的展示,不影响整个水平滚动视图:

publicclassHSVAdapterextendsBaseAdapter{privatestaticfinalStringTAG="HSV";privateList<Map<String,Object>>lstAvatars;privateContextcontext;privateintlayoutWidthPerAvatar;publicHSVAdapter(Contextcontext,intlayoutWidthPerAvatar){this.context=context;this.lstAvatars=newArrayList<Map<String,Object>>();this.layoutWidthPerAvatar=layoutWidthPerAvatar;}@OverridepublicintgetCount(){returnlstAvatars.size();}@OverridepublicMap<String,Object>getItem(intlocation){returnlstAvatars.get(location);}@OverridepubliclonggetItemId(intarg0){returnarg0;}publicvoidaddObject(Map<String,Object>map){lstAvatars.add(map);notifyDataSetChanged();}@OverridepublicViewgetView(intlocation,Viewarg1,ViewGrouparg2){Viewview=LayoutInflater.from(context).inflate(R.layout.user_avatar,null);view.setLayoutParams(newViewGroup.LayoutParams(layoutWidthPerAvatar,ViewGroup.LayoutParams.WRAP_CONTENT));TextViewtvIndex=(TextView)view.findViewById(R.id.index_tv);tvIndex.setText("index-"+String.valueOf(location));returnview;}}

其对应的布局文件 user_avatar.xml 如下:

<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"><ImageViewandroid:layout_width="64dp"android:layout_height="64dp"android:src="@drawable/avatar"/><TextViewandroid:id="@+id/index_tv"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"/></LinearLayout>

最后看一下主页面 MainActivity:

publicclassMainActivityextendsActionBarActivityimplementsINotifySelectItem{privatestaticfinalStringTAG="Main";privateHorizontalScrollViewhsv;privateHSVLayoutlayoutAvatar;privateHSVAdapteradapterAvatar;privateTextViewtvScrollX;privateintlayoutWidthPerAvatar=0;privateInteger[]p_w_picpaths={R.drawable.avatar,R.drawable.avatar,R.drawable.avatar,R.drawable.avatar,R.drawable.avatar,R.drawable.avatar,R.drawable.avatar,R.drawable.avatar,R.drawable.avatar};//记录当前居中的头像索引privateintcurrentIndex=1;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);intwidth=DisplayUtil.getScreenWidth(this);intlayoutWidth=(int)(width-getResources().getDimension(R.dimen.activity_horizontal_margin)*2);//每一个头像占用的宽度layoutWidthPerAvatar=layoutWidth/3;hsv=(HorizontalScrollView)findViewById(R.id.hsv);hsv.setOnTouchListener(newView.OnTouchListener(){privateintlastScrollX=0;privateintTouchEventId=-9987832;Handlerhandler=newHandler(){@OverridepublicvoidhandleMessage(Messagemsg){super.handleMessage(msg);if(msg.what==TouchEventId){if(lastScrollX==hsv.getScrollX()){//停止滚动,计算合适的位置(采用四舍五入)intindexScrollTo=Math.round(lastScrollX/(layoutWidthPerAvatar*1.0f));Log.d(TAG,"stopscroll-"+lastScrollX+"|"+layoutWidthPerAvatar+"|"+lastScrollX/(layoutWidthPerAvatar*1.0f)+"|"+indexScrollTo);if(indexScrollTo>0){hsv.smoothScrollTo(indexScrollTo*layoutWidthPerAvatar,0);}else{hsv.smoothScrollTo(0,0);}}else{handler.sendMessageDelayed(handler.obtainMessage(TouchEventId),100);lastScrollX=hsv.getScrollX();}}}};@OverridepublicbooleanonTouch(Viewv,MotionEventevent){Log.d(TAG,"touchevent-action:"+event.getAction()+"|"+event.getX()+"|"+event.getY()+"|"+hsv.getScrollX()+"|"+hsv.getScrollY());if(event.getAction()==MotionEvent.ACTION_UP){handler.sendMessageDelayed(handler.obtainMessage(TouchEventId),100);}returnfalse;}});layoutAvatar=(HSVLayout)findViewById(R.id.avatar_layout);adapterAvatar=newHSVAdapter(this,layoutWidthPerAvatar);for(inti=0;i<p_w_picpaths.length;i++){Map<String,Object>map=newHashMap<String,Object>();map.put("p_w_picpath",p_w_picpaths[i]);map.put("index",(i+1));adapterAvatar.addObject(map);}layoutAvatar.setAdapter(layoutWidthPerAvatar,adapterAvatar,this);tvScrollX=(TextView)findViewById(R.id.scrollx_tv);}@OverridepublicbooleanonCreateOptionsMenu(Menumenu){//Inflatethemenu;thisaddsitemstotheactionbarifitispresent.getMenuInflater().inflate(R.menu.menu_main,menu);returntrue;}@OverridepublicbooleanonOptionsItemSelected(MenuItemitem){//Handleactionbaritemclickshere.Theactionbarwill//automaticallyhandleclicksontheHome/Upbutton,solong//asyouspecifyaparentactivityinAndroidManifest.xml.intid=item.getItemId();//noinspectionSimplifiableIfStatementif(id==R.id.action_settings){returntrue;}returnsuper.onOptionsItemSelected(item);}@Overridepublicvoidselect(intposition){Toast.makeText(this,"select"+String.valueOf(position),Toast.LENGTH_SHORT).show();if(position>0){if(currentIndex!=position){hsv.smoothScrollTo((position-1)*layoutWidthPerAvatar,0);currentIndex=position;}}}publicvoidonClickScrollX(Viewview){tvScrollX.setText("Scroll.x="+String.valueOf(hsv.getScrollX()));}}