案例四 自定义开关:
功能介绍:本案例实现的功能是创建一个自定义的开关,可以自行决定开关的背景。当滑动开关时,开关的滑块可跟随手指移动。当手指松开后,滑块根据开关的状态,滑到最右边或者滑到最左边,同时保存开关的状态,将开关的状态回调给调用者。当然,上述功能系统给定的switch控件也可以实现。
实现步骤:
1. 写一个类继承view,重写两个参数的构造方法。在构造方法中指定工作空间,通过attrs.getAttributeResourceValue方法将java代码中的属性值和xml中的属性值联系起来。这样可以在xml文件中指定相关的属性值。重写onmeasure和ondraw方法,绘制图片。这里测量图片大小直接用setMeasuredDimension方法,获取图片本身的大小。
2. 设置接口回调。对于图片来说,我们希望能够在调用者获取开关的状态,因此需要设置一个接口回调,用于监控开关的状态,当开关的状态发生变化时间调用。接口回调的优势在于调用者并不知道何时调用,所以在另一个文件中设置一个接口,在该文件触发事件。由于重写了接口的方法,因此,执行重写后的方法。这样就可以实现数据的回调。自定义控件中接口回调的应用较为广泛,几乎所有的控件都需要设置监听,且写法较为固定。
3. 重写ontouchevent()方法。分析得知,开关由两部分组成,一部分是底座儿,一部分是划片而。当手指滑动时,划片儿应该跟随手指移动。当划片的左边小于底座左边坐标时,让划片左边的坐标和底座对齐,当划片的右边大于底座右边坐标时,让划片右边的坐标和底座对齐,这样保证划片不越界。当手指松开后,判断划片的中线坐标是在底座儿中线坐标的左边还是右边,以此来决定划片最终是停在左边还是右边。同时改变开关的状态,将开关的状态回调给控件的调用中,读取开关的状态。
代码实现。 主程序(调用者)中的代码:
package com.example.aswitch;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.Toast;public class MainActivity extends AppCompatActivity {private MyToggleButton toggleButton;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);toggleButton = (MyToggleButton) findViewById(R.id.toggle_button);toggleButton.setOnStateChangedListener(new MyToggleButton.OnStateChangedListener() {@Overridepublic void onStateChanged(boolean state) {Toast.makeText(MainActivity.this, state ? "开" : "关", Toast.LENGTH_SHORT).show();}});}}
自定义开关的具体实现;
package com.example.aswitch;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;/** * Created by huang on 2016/12/1. */public class MyToggleButton extends View {private Bitmap background;private Bitmap slideIcon;private boolean state;private OnStateChangedListener mOnStateChangedListener;private int backgroundWidth;private int backgroundHeight;private int slideIconWidth;private int slideIconHeight;private int slideIconLeft;private int maxSlideIconLeft;public MyToggleButton(Context context, AttributeSet attrs) {super(context, attrs);String namespace = "http://schemas.android.com/apk/res-auto";int slideBackgroundResId = attrs.getAttributeResourceValue(namespace, "slideBackground", -1);int slideIconResId = attrs.getAttributeResourceValue(namespace, "slideIcon", -1);if (slideBackgroundResId != -1 && slideIconResId != -1) {setSwitchImage(slideBackgroundResId, slideIconResId);}boolean state = attrs.getAttributeBooleanValue(namespace, "state", false);setState(state);}/** * 设置开关的图片 * @param slideBackgroundResId 开关的背景图片资源id * @param slideIconResId 开关上面的滑块icon */public void setSwitchImage(int slideBackgroundResId, int slideIconResId) {background = BitmapFactory.decodeResource(getResources(), slideBackgroundResId);slideIcon = BitmapFactory.decodeResource(getResources(), slideIconResId);backgroundWidth = background.getWidth();backgroundHeight = background.getHeight();slideIconWidth = slideIcon.getWidth();slideIconHeight = slideIcon.getHeight();maxSlideIconLeft = backgroundWidth - slideIconWidth;}/** 设置开关按钮的状态 */public void setState(boolean state) {checkState(state);if (state) {slideIconLeft = maxSlideIconLeft;} else {slideIconLeft = 0;}}public void setOnStateChangedListener(OnStateChangedListener mOnStateChangedListener) {this.mOnStateChangedListener = mOnStateChangedListener;}/** 开关按钮状态改变的监听器 */public interface OnStateChangedListener {void onStateChanged(boolean state);}/** * 对View进行测量的方法 */@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(backgroundWidth, backgroundHeight);}/** 把View画出来的方法 * @param canvas 画布 * */@Overrideprotected void onDraw(Canvas canvas) {int left = 0;int top = 0;canvas.drawBitmap(background, left, top, null);canvas.drawBitmap(slideIcon, slideIconLeft, 0, null);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_MOVE:slideIconLeft = (int) (event.getX() - slideIconWidth / 2);if (slideIconLeft < 0) {slideIconLeft = 0;} else if (slideIconLeft > maxSlideIconLeft) {slideIconLeft = maxSlideIconLeft;}break;case MotionEvent.ACTION_UP:if (event.getX() < backgroundWidth / 2) {slideIconLeft = 0;checkState(false);} else {slideIconLeft = maxSlideIconLeft;checkState(true);}break;}invalidate();return true;}private void checkState(boolean state) {if (this.state != state) {this.state = state;if (mOnStateChangedListener != null) {mOnStateChangedListener.onStateChanged(state);}}}}
布局文件的编写:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:huang="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"><com.example.aswitch.MyToggleButtonandroid:id="@+id/toggle_button"android:layout_width="wrap_content"android:layout_height="wrap_content"huang:slideBackground="@mipmap/slide_background2"huang:slideIcon="@mipmap/slide_icon2"huang:state="false" /></RelativeLayout>
为了使布局文件中的属性有作用,还要单独在values文件夹中写一个attrs.xml文件,声明相关的属性。
<?xml version="1.0" encoding="utf-8"?><resources><declare-styleable name="MyToggleButton"><attr name="slideBackground" format="reference" /><attr name="slideIcon" format="reference" /><attr name="state" format="boolean" /></declare-styleable></resources>
案例五 下拉刷新的Listview:功能介绍: 系统本身的Listview默认情况下是没有下拉刷新和上拉加载更多的功能。但是Listview有addHeadview和addFootView方法,因此可以为listview加头布局或者脚布局。对于头布局来说,有三个状态,下拉刷新、松开刷新、正在刷新,同时伴有相应的动画予以提示。刷新成功后,为listview添加一套信息。同样的脚布局加载成功后,在listview的最后一条数据后面,在加载一条信息。
实现步骤:
1. 写布局文件。这里除了在主界面中需要一个Listview外,还需要一个头部的布局文件和一个尾部的布局文件。初始状态时,头布局和脚布局均是隐藏的。当滑动事件出发的时候,调整头布局和脚布局的位置。这里。头布局和脚布局的根表签最好是使用Linearlayout进行包裹,否则在滑动显示是容易出现问题。
2. 写一个类继承listview。初始化界面布局,通过View.measure(0, 0)方法主动触发测量,mesure内部会调用onMeasure,从而获取到头布局的高度。如步骤一所说,开始状态时,头布局是隐藏的,所以为头布局设置panding值,来使得头布局隐藏,只需要把setpadding中的第二个参数设置为负的头文件的高。同样的,显示头布局只需要把setpadding中的第二个参数设置为0。对于根布局的显示和隐藏同样采用上述的方法。当然也可以改变setpadding的第四个参数来控制显示和隐藏。
3. 重写OntouchEvent()方法。手指按下时,记录下初始位置。手指滑动时在记录一个y坐标。通过两次坐标的差值,判断手指滑动的方向。在可见的第一个条目的position为0的时候,如果手指向下滑动,有两种状态,一个是下拉刷新,指的是头布局从不可见到刚好全部可见,一个是松开刷新,头布局全部可见后继续向下拖动,就会进入松开刷新状态。另外还有一种状态就是正在刷新,这种状态下,头布局刚好停留在顶部,维持一段时间。实际开发中,正在刷新是向服务器请求网络。这里添加的是假数据。不同的状态之间的转换还伴随有动画效果,因此,这里还写有补间动画来实现向上和向下的效果。
4. 写一个滑动监听事件,在监听事件中进行脚布局逻辑的编写。当满足以下三种情况的时候则显示脚布局,一个ListView处于空闲状态,另一个是界面上可见的最后一条item是ListView中最后的一条item,还有一个是如果当前没有去做正在加载更多的事情。显示脚布局的时候,不仅要设置padding值,同时选中listview最后一个条目,这样就可以在界面上完整的显示。
5. 最后要注意的是,无论是同布局,还是脚布局,都要对状态进行修改。所以,可以增加一个回调监听。表明数据刷新或者加载完成,可以隐藏头布局和脚布局了,同时,头布局的状态恢复为下拉刷新,脚布局不是正在加载。同时,停掉相关的动画,修改显示的状态。
主程序(调用者)的编写。一般刷新是请求数据库数据,这里直接给出模拟数据演示效果。
package com.example.pulltofreshlistview;import android.os.Bundle;import android.os.Handler;import android.support.v7.app.AppCompatActivity;import android.widget.ArrayAdapter;import com.example.pulltofreshlistview.view.PullToRefreshListView;import java.util.ArrayList;public class MainActivity extends AppCompatActivity {private PullToRefreshListView listView;private ArrayList<String> datas;private ArrayAdapter<String> adapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);listView = (PullToRefreshListView) findViewById(R.id.list_view);datas = new ArrayList<String>();for (int i = 0; i < 20; i++) {datas.add("我又捡到钱了,好开心啊^_^ " + i);}adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, datas);listView.setAdapter(adapter);listView.setOnRefreshingListener(new PullToRefreshListView.OnRefreshingListener() {@Overridepublic void onRefreshing() {reloadData();}@Overridepublic void onLoadMore() {loadMore();}});}/** * 重新联网获取数据 */protected void reloadData() {new Handler().postDelayed(new Runnable() {@Overridepublic void run() {datas.add(0, "我是刷新出来的数据");adapter.notifyDataSetChanged();listView.onRefreshComplete();}}, 3000);}/** * 联网加载更多数据 */protected void loadMore() {new Handler().postDelayed(new Runnable() {@Overridepublic void run() {datas.add("我是加载更多出来的数据");adapter.notifyDataSetChanged();listView.onLoadmoreComplete();}}, 3000);}}
下拉刷新listview的逻辑部分
package com.example.pulltofreshlistview.view;import android.content.Context;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.animation.RotateAnimation;import android.widget.AbsListView;import android.widget.ImageView;import android.widget.ListView;import android.widget.ProgressBar;import android.widget.TextView;import com.example.pulltofreshlistview.R;/** * Created by huang on 2016/12/1. */public class PullToRefreshListView extends ListView {private View headerView;private float downY;private int headerViewHeight;private static final int STATE_PULL_TO_REFRESH = 0;private static final int STATE_RELEASE_REFRESH = 1;private static final int STATE_REFRESHING = 2;private int currentState = STATE_PULL_TO_REFRESH;// 默认是下拉刷新状态private ImageView iv_arrow;private ProgressBar progress_bar;private TextView tv_state;private RotateAnimation upAnim;private RotateAnimation downAnim;private OnRefreshingListener mOnRefreshingListener;private View footerView;private int footerViewHeight;private boolean loadingMore;public PullToRefreshListView(Context context, AttributeSet attrs) {super(context, attrs);initHeaderView();initFooterView();}private void initHeaderView() {headerView = View.inflate(getContext(), R.layout.header_view, null);iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);progress_bar = (ProgressBar) headerView.findViewById(R.id.progress_bar);showRefreshingProgressBar(false);tv_state = (TextView) headerView.findViewById(R.id.tv_state);headerView.measure(0, 0);headerViewHeight = headerView.getMeasuredHeight();hideHeaderView();super.addHeaderView(headerView);upAnim = createRotateAnim(0f, -180f);downAnim = createRotateAnim(-180f, -360f);}private void initFooterView() {footerView = View.inflate(getContext(), R.layout.footer_view, null);footerView.measure(0, 0);footerViewHeight = footerView.getMeasuredHeight();hideFooterView();super.addFooterView(footerView);super.setOnScrollListener(new OnScrollListener() {@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {if (scrollState == OnScrollListener.SCROLL_STATE_IDLE&& getLastVisiblePosition() == getCount() - 1&& loadingMore == false) {loadingMore = true;showFooterView();setSelection(getCount() - 1);if (mOnRefreshingListener != null) {mOnRefreshingListener.onLoadMore();}}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}});}private void hideFooterView() {int paddingTop = -footerViewHeight;setFooterViewPaddingTop(paddingTop);}private void showFooterView() {int paddingTop = 0;setFooterViewPaddingTop(paddingTop);}private void setFooterViewPaddingTop(int paddingTop) {footerView.setPadding(0, paddingTop, 0, 0);}/** * 设置显示进度的圈圈 * * @param showProgressBar 如果是true,则显示ProgressBar,否则的话显示箭头 */private void showRefreshingProgressBar(boolean showProgressBar) {progress_bar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);iv_arrow.setVisibility(!showProgressBar ? View.VISIBLE : View.GONE);if (showProgressBar) {iv_arrow.clearAnimation();// 有动画的View要清除动画才能真正的隐藏}}/** * 创建旋转动画 * * @param fromDegrees 从哪个角度开始转 * @param toDegrees转到哪个角度 * @return */private RotateAnimation createRotateAnim(float fromDegrees, float toDegrees) {int pivotXType = RotateAnimation.RELATIVE_TO_SELF;int pivotYType = RotateAnimation.RELATIVE_TO_SELF;float pivotXValue = 0.5f;float pivotYValue = 0.5f;RotateAnimation ra = new RotateAnimation(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue);ra.setDuration(300);ra.setFillAfter(true);return ra;}/** * 隐藏HeaderView */private void hideHeaderView() {int paddingTop = -headerViewHeight;setHeaderViewPaddingTop(paddingTop);}/** * 显示HeaderView */private void showHeaderView() {int paddingTop = 0;setHeaderViewPaddingTop(paddingTop);}/** * 设置HeaderView的paddingTop * * @param paddingTop */private void setHeaderViewPaddingTop(int paddingTop) {headerView.setPadding(0, paddingTop, 0, 0);}@Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:downY = ev.getY();break;case MotionEvent.ACTION_MOVE:if (currentState == STATE_REFRESHING) {return super.onTouchEvent(ev);}int fingerMoveDistanceY = (int) (ev.getY() - downY);// 手指移动的距离if (fingerMoveDistanceY > 0 && getFirstVisiblePosition() == 0) {int paddingTop = -headerViewHeight + fingerMoveDistanceY;setHeaderViewPaddingTop(paddingTop);if (paddingTop < 0 && currentState != STATE_PULL_TO_REFRESH) {currentState = STATE_PULL_TO_REFRESH;tv_state.setText("下拉刷新");iv_arrow.startAnimation(downAnim);showRefreshingProgressBar(false);} else if (paddingTop >= 0 && currentState != STATE_RELEASE_REFRESH) {currentState = STATE_RELEASE_REFRESH;tv_state.setText("松开刷新");iv_arrow.startAnimation(upAnim);showRefreshingProgressBar(false);}return true;}break;case MotionEvent.ACTION_UP:if (currentState == STATE_RELEASE_REFRESH) {currentState = STATE_REFRESHING;tv_state.setText("正在刷新");showRefreshingProgressBar(true);showHeaderView();if (mOnRefreshingListener != null) {mOnRefreshingListener.onRefreshing();}} else if (currentState == STATE_PULL_TO_REFRESH) {hideHeaderView();}break;}return super.onTouchEvent(ev);}public void setOnRefreshingListener(OnRefreshingListener mOnRefreshingListener) {this.mOnRefreshingListener = mOnRefreshingListener;}/** * ListView刷新的监听器 */public interface OnRefreshingListener {void onRefreshing();void onLoadMore();}/** * 联网刷新数据的操作已经完成了 */public void onRefreshComplete() {hideHeaderView();currentState = STATE_PULL_TO_REFRESH;showRefreshingProgressBar(false);}/** * 加载更多新数据的操作已经完成了 */public void onLoadmoreComplete() {hideFooterView();loadingMore = false;}}
布局文件包含三个部分,listview主体部分:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns: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"tools:context=".MainActivity"><com.example.pulltofreshlistview.view.PullToRefreshListViewandroid:id="@+id/list_view"android:layout_width="match_parent"android:layout_height="match_parent" /></RelativeLayout>
头布局:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="center_vertical" ><RelativeLayoutandroid:layout_width="50dp"android:layout_height="50dp"><ImageViewandroid:id="@+id/iv_arrow"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/arrow"android:layout_centerInParent="true"/><ProgressBarandroid:id="@+id/progress_bar"style="@android:style/Widget.ProgressBar"android:indeterminateDrawable="@drawable/progress_medium_red"android:layout_width="28dp"android:layout_height="28dp"android:layout_centerInParent="true"android:visibility="gone"/></RelativeLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:orientation="vertical"><TextViewandroid:id="@+id/tv_state"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="#FF0000"android:text="下拉刷新"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="#666666"android:textSize="12sp"android:text="最后刷新时间:2015-07-25 19:59:39"android:layout_marginTop="4dp"/></LinearLayout></LinearLayout>
脚布局:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:orientation="horizontal"><ProgressBarstyle="@android:style/Widget.ProgressBar"android:layout_width="28dp"android:layout_height="28dp"android:layout_marginBottom="8dp"android:layout_marginTop="8dp"android:indeterminateDrawable="@drawable/progress_medium_red" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="6dp"android:text="加载更多..."android:textColor="#FF0000" /></LinearLayout>
案例六 侧滑菜单:
功能分析: 之前的案例大部分都只有一个控件,因此是通过继承view实现。侧滑菜单显然需要两个控件才能实现,一个用于加载主界面,一个用于加载侧滑界面。所以这里需要继承viewgroup。通过在主界面滑动来控制界面的显示和隐藏。
实现步骤:
1. 布局文件的书写。布局文件包含两个部分,主界面和侧滑界面。主界面中有一个标题栏,标题栏有图片按钮,文字组成。图片按牛同样可以控制侧边栏的显示和隐藏。侧边栏在ScrollView中添加一个Linearlayout,在linearlayout中纵向排列textview显示文本。当然,侧滑菜单同样可以用listview显示,这里为简单起见就不采用listview显示。此外,TextView要想响应点击事件,需要设置clickable为true。
2. 创建一个类继承于ViewGroup。重写onmeasure()方法,测量测量控件大小,包括两个子控件。这里,侧滑菜单的宽在布局文件中写好了,主界面的宽适配手机界面。所以分别调用menu.measure(menuWidth, heightMeasureSpec)和main.measure(widthMeasureSpec, heightMeasureSpec)方法即可获取。排版容器中的子View。由于该自定义的控件包含不止一个子view,所以重写onlayout()方法是必不可少的。对子View进行排版,子View的0,0坐标是SlidingMenu的左上角。对侧滑菜单进行排版,菜单的left坐标在负的菜单宽的位置,菜单的top坐标在0的位置,菜单的right坐标在0的位置,菜单的bottom坐标在容器的最底边。对主界面进行排版,主界面的left坐标在0的位置,主界面的top坐标在0的位置,主界面的right坐标在容器的最右边,主界面的bottom坐标在容器的最底边。
3. 重写onInterceptTouchEvent。本案例中只关心水平划动,所以如果水平移动距离比垂直移动距离大,则认为是水平移动,把事件拦截,不让ScrollView使用,此事件交由控件本身处理。这里有必要介绍一下事件分发。视图集合对于事件的分发,自上而下处理。ViewGroup拥有下面3个Touch相关方法,dispatchTouchEvent(MotionEvent ev)用于Touch事件的颁发,onInterceptTouchEvent(MotionEvent ev) 用于拦截Touch事件,onTouchEvent(MotionEvent event) 用于处理Touch事件。这里重写了onInterceptTouchEvent(),相应的还要重写ontouchevent()方法。
4. 在ontouchevent()方法中实现滑动跟随的逻辑。滑动事件中,只关心横向滑动。按下时,记录下当前的x坐标。滑动时,再次记录当前x坐标,两者相减得到移动的距离。通过scrollTo()方法滑到相应的位置。系统给定的scrollTo()方法默认向右为负值,所以可重写scrollTo()方法保证移动为正值。代码优化。其实上述过程可以实现侧滑菜单的功能。当滑动过程略显生硬。这时就可以用到Scroller,这个类专门用于模拟滚动的数值。同时要配合computeScroll()方法使用。
5. 还有一个菜单的开关按钮,要么开,要么关。开的时候完全显示侧滑菜单。关的时候,隐藏侧滑菜单。这点逻辑和ontouchevent()中手指抬起的逻辑一样。
主程序(调用者)的编写:
package com.example.slidingmenu;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.view.Window;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends Activity {private SlidingMenu sliding_menu;private LinearLayout ll_menu;private TextView tv_news;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_main);sliding_menu = (SlidingMenu) findViewById(R.id.sliding_menu);ll_menu = (LinearLayout) findViewById(R.id.ll_menu);tv_news = (TextView) findViewById(R.id.tv_news);setCurrentSelectedMenuItem(tv_news);}/** 设置当前选择的菜单Item */private void setCurrentSelectedMenuItem(View menuItem) {for (int i = 0; i < ll_menu.getChildCount(); i++) {View child = ll_menu.getChildAt(i);child.setSelected(child == menuItem);}}/** 菜单列表中的某个菜单项被单击了 */public void onMenuItemClick(View v) {TextView textView = (TextView) v;Toast.makeText(this, textView.getText(), Toast.LENGTH_SHORT).show();setCurrentSelectedMenuItem(v);}/** 主界面上的菜单按钮被单击了 */public void onMenuToggleClick(View v) {sliding_menu.toggle();}}
侧滑菜单的主体逻辑。
package com.example.slidingmenu;import android.content.Context;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.Scroller;/** * Created by huang on 2016/12/1. */public class SlidingMenu extends ViewGroup {private View menu;private View main;private int menuWidth;private int downX;private int currentX;/** 这个类专门用于模拟滚动的数值 */private Scroller scroller;public SlidingMenu(Context context, AttributeSet attrs) {super(context, attrs);scroller = new Scroller(context);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);// 测量容器自己的宽高menu = getChildAt(0); // 获取菜单容器main = getChildAt(1); // 获取主界面容器menuWidth = menu.getLayoutParams().width; // 获取菜单的宽// 测量菜单menu.measure(menuWidth, heightMeasureSpec);// 测量主界面main.measure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int menuLeft = -menuWidth;int menuTop = 0;int menuRight = 0;int menuBottom = b - t;menu.layout(menuLeft, menuTop, menuRight, menuBottom);int mainLeft = 0;int mainTop = 0;int mainRight = r - l;int mainBottom = b - t;main.layout(mainLeft, mainTop, mainRight, mainBottom);}/** * 让界面滚动到x的位置,传正数往右移,传负往左移 * @param x */public void scrollTo(int x) {super.scrollTo(-x, 0);}/** 获取当前滑动到的位置 */public int getMyScrollX() {return -super.getScrollX();}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:downY = (int) ev.getY();downX = (int) ev.getX();break;case MotionEvent.ACTION_MOVE:int distanceX = Math.abs((int) (ev.getX() - downX));int distanceY = Math.abs((int) (ev.getY() - downY));if (distanceX > distanceY) {return true;}break;}return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:downX = (int) event.getX();break;case MotionEvent.ACTION_MOVE:int fingerMoveDistanceX = (int) event.getX() - downX;int destX = currentX + fingerMoveDistanceX;if (destX < 0) {destX = 0;} else if (destX > menuWidth){destX = menuWidth;}scrollTo(destX);break;case MotionEvent.ACTION_UP:if (getMyScrollX() < menuWidth / 2) {startScroll(0);} else {startScroll(menuWidth);}break;}return true;}int count;private int downY;/** * 以动画的方式滚动到指定的位置 * * @param destX 要滑动到哪里(目标位置) */private void startScroll(int destX) {currentX = destX;int startX = getMyScrollX();int distatnceX = destX - startX;int duration = 800;scroller.startScroll(startX, 0, distatnceX, 0, duration);invalidate();}@Overridepublic void computeScroll() {if (scroller.computeScrollOffset()) {int currX = scroller.getCurrX();scrollTo(currX);invalidate();count++;}System.out.println("count = " + count);}/** 菜单的开关按钮,要么开,要么关 */public void toggle() {if (getMyScrollX() > 0) {startScroll(0);} else {startScroll(menuWidth);}}}
整体布局
<?xml version="1.0" encoding="utf-8"?><com.example.slidingmenu.SlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/sliding_menu"android:layout_width="match_parent"android:layout_height="match_parent"><!-- 在SlidingMenu中的索引0 --><include layout="@layout/menu" /><!-- 在SlidingMenu中的索引1 --><include layout="@layout/main" /></com.example.slidingmenu.SlidingMenu>
主界面布局
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="@mipmap/top_bar_bg"android:gravity="center_vertical"android:orientation="horizontal"><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="onMenuToggleClick"android:src="@mipmap/main_back" /><Viewandroid:layout_width="1dp"android:layout_height="match_parent"android:layout_marginBottom="6dp"android:layout_marginTop="6dp"android:background="@mipmap/top_bar_divider" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="12dp"android:text="新闻"android:textColor="@android:color/white"android:textSize="34sp" /></LinearLayout><TextViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:text="为了一个小馒头,友谊的小船说翻就翻"android:textSize="30sp" /></LinearLayout>
侧滑界面布局
<?xml version="1.0" encoding="utf-8"?><ScrollView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="240dp"android:layout_height="match_parent"android:background="@mipmap/menu_bg"><LinearLayoutandroid:id="@+id/ll_menu"android:layout_width="240dp"android:layout_height="wrap_content"android:orientation="vertical" ><TextViewandroid:id="@+id/tv_news"android:text="新闻"style="@style/menu_item"android:drawableLeft="@mipmap/tab_news"/><TextViewandroid:text="订阅"style="@style/menu_item"android:drawableLeft="@mipmap/tab_read"/><TextViewandroid:text="本地"style="@style/menu_item"android:drawableLeft="@mipmap/tab_local"/><TextViewandroid:text="跟贴"style="@style/menu_item"android:drawableLeft="@mipmap/tab_ties"/><TextViewandroid:text="图片"style="@style/menu_item"android:drawableLeft="@mipmap/tab_pics"/><TextViewandroid:text="话题"style="@style/menu_item"android:drawableLeft="@mipmap/tab_ugc"/><TextViewandroid:text="投票"style="@style/menu_item"android:drawableLeft="@mipmap/tab_vote"/><TextViewandroid:text="聚合阅读"style="@style/menu_item"android:drawableLeft="@mipmap/tab_focus"/></LinearLayout></ScrollView>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。