位置:首页 » 技术 » UI-Android中的状态切换旋钮自定义

UI-Android中的状态切换旋钮自定义

日期:2015-07-01 阅读:0num
Advertisement

UI--Android中的状态切换按钮自定义

《代码里的世界》UI篇

用文字札记描绘自己 android学习之路

转载请保留出处 by Qiao
http://blog.csdn.net/qiaoidea/article/details/46715453

1.概述

  Android中关于控制开关和页面/状态切换的使用场景还是比较多的。源生做的支持也有比如RadioGroup 和Tabhost等。这里准备通过自定义View来模仿学习下IOS两种常见UI样式: SwitchButtonSegmentControl
  首先先通过简易的组装View来实现两种UI的相应效果,其次呢,尝试通过绘制来达到同样的更灵活的样式。代码前后共实现按钮切换和页面切换两个样式,三种实现方案,其中,两种SwitchButton实现,一种SegmentControl实现。实现方案中关于自定义View绘制,本篇只讲述SwitchView,希望大家能举一反三,同样做到SegmentControl的相同效果。个人也更倾向于使用自定义实现,更方便灵活。
  先看效果图:
  UI-Android中的状态切换旋钮自定义

  头部即为切换页面的SegmentControl,然后第一行是通过组装view来实现SwitchButton,第二行则是完全绘制出来的SwitchButton效果。接下来我们分别一一讲述代码实现。


2.SwitchButton样式两种实现

  状态开关按钮常用于某些控制开关,设置选项里最为常见。

2.1 组合View实现

  该方法比较简单明了,定义三个view,开启状态和关闭状态两个背景View,一个圆形按钮view。点击时候利用滑动动画移动按钮和状态背景,达到类似的视觉效果。
  先看xml布局:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <ImageView
            android:id="@+id/on_bg_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/switch_on_bg" />

        <ImageView
            android:id="@+id/off_bg_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/switch_off_bg" />
    </FrameLayout>

    <ImageView
        android:id="@+id/circle_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/switch_circle" />

</merge>

  因为是帧布局,所以顶层使用merge(merge简化xml不解释,自行百度)。然后使用两个开关状态背景和一个圆形按钮组合而成。

1. 全局变量参数

public class SwitchView extends FrameLayout {
    protected boolean isChecked;  //是否选中状态
    protected View onBgView;
    protected View offBgView;
    protected View circleView;
    protected boolean autoForPerformClick = true; //是否允许点击自动切换
    protected OnCheckedChangedListener onCheckedChangedListener; //切换事件监听

    //...
}

  一般状态切换是由click事件监听,根据业务逻辑来判断是否切换状态。但对于switchButton,通常我们操作时直观感受应该是先切换了状态才执行相应操作的,所以我们在performClick事件中直接根据autoForPerformClick 的状态来相应点击操作。

至于performClick ,其实就是控制条用onClickListener的方法体,具体逻辑在View源码中查看。

2. 初始化

    public SwitchView(Context context) {
        super(context);
        initialize();
    }

    public SwitchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    public SwitchView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize();
    }

    protected void initialize() {
        setClickable(true);
        LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        layoutInflater.inflate(R.layout.switch_view, this);
        onBgView = findViewById(R.id.on_bg_view);
        offBgView = findViewById(R.id.off_bg_view);
        circleView = findViewById(R.id.circle_view);
    }

3. 点击响应

    @Override
    public boolean performClick() {
        if (!autoForPerformClick) //如果不是自动响应则调用默认处理方法
            return super.performClick();
        /**
        *否则直接切换switch状态并触发事件监听
        */
        setChecked(!isChecked, true);
        if (onCheckedChangedListener != null) {
            onCheckedChangedListener.onChanged(this, isChecked);
        }
        return super.performClick();
    }

  View点击后会执行performClick方法,并判断是否调用clickLisentener。这里我们直接重写performClick方法,如果自动响应autoForPerformClick为ture则直接切换Switch状态,否则调用默认处理逻辑。

4.切换状态动画

  点击打开,则圆形按钮从左端滑动到右端,onBg显示,offBg隐藏;
  再点击关闭,圆形按钮从右端滑动到左端,onBg隐藏,offBg显示。

public void setChecked(boolean value, boolean needAnimate) {
        if (isChecked == value)
            return;
        isChecked = value;

        float targetX = 0; //要移动的目标位置
        if (getWidth() != 0) {  //当前view没有渲染上去时候,getWidth()为零
            targetX = getWidth() - circleView.getWidth();
        } else {
            measure(0, 0);
            targetX = getMeasuredWidth() - circleView.getMeasuredWidth();
        }

        long durationMillis = needAnimate ? 200 : 0;
        if (isChecked) {
            onBgView.bringToFront(); //显示在最前端
            onBgView.setVisibility(View.VISIBLE);
            offBgView.setVisibility(View.VISIBLE);

            //平移动画
            TranslateAnimation an1 = new TranslateAnimation(0, targetX, 0, 0);
            an1.setFillAfter(true);
            an1.setDuration(durationMillis);
            circleView.startAnimation(an1);

            //透明度动画
            AlphaAnimation an2 = new AlphaAnimation(0, 1);
            an2.setFillAfter(true);
            an2.setDuration(durationMillis);
            onBgView.startAnimation(an2);
        } else {
            offBgView.bringToFront();
            onBgView.setVisibility(View.VISIBLE);
            offBgView.setVisibility(View.VISIBLE);

            TranslateAnimation an1 = new TranslateAnimation(targetX, 0, 0, 0);
            an1.setFillAfter(true);
            an1.setDuration(durationMillis);
            circleView.startAnimation(an1);

            AlphaAnimation an2 = new AlphaAnimation(0, 1);
            an2.setFillAfter(true);
            an2.setDuration(durationMillis);
            offBgView.startAnimation(an2);
        }
    }

  状态切换的两个参数,value是否打开状态,needAnimate是否需要动画(否则直接切换效果)。setFillAfter保留动画结束状态,但并不影响View本身位置和状态。切换时,先将当前显示背景移动到最前端,其次添加按钮动画和渐隐动画。
  至此,最基本的组合View实现已经完成了。想要了解详情的请在源码中查看。源码分为两部分,一个项目是View的实现lib,另一块是示例演示demo.
  

2.2 自定义View绘制实现

  由于该样式并不十分复杂,所以可以通过基本的图形绘制draw出同样的效果。
  具体实现逻辑:通过自定view属性来确定按钮大小和中间圆钮大小,在测量onMesure方法中控制测量值mode和Size,并在onLayout方法中得到圆钮半径和起始点位置。然后进行绘制,先绘制底部on圆角矩形背景,再绘制off渐变缩放的圆角矩形,最后绘制spot圆钮。
  嘴比较笨拙,又不会画图。用word的图形工具将就画下可以看就好了。
  UI-Android中的状态切换旋钮自定义

  具体实现大体都类似,这里贴上主要部分代码

1.全局参数 

public class SwitchButton extends View{
    /** */
    private float radius;
    /** 开启颜色*/
    private int onColor = Color.parseColor("#4ebb7f");
    /** 关闭颜色*/
    private int offBorderColor = Color.parseColor("#dadbda");
    /** 灰色带颜色*/
    private int offColor = Color.parseColor("#ffffff");
    /** 手柄颜色*/
    private int spotColor = Color.parseColor("#ffffff");
    /** 边框颜色*/
    private int borderColor = offBorderColor;
    /** 画笔*/
    private Paint paint ;
    /** 开关状态*/
    private boolean toggleOn = false;
    /** 边框大小*/
    private int borderWidth = 2;
    /** 垂直中心*/
    private float centerY;
    /** 按钮的开始和结束位置*/
    private float startX, endX;
    /** 手柄X位置的最小和最大值*/
    private float spotMinX, spotMaxX;
    /**手柄大小 */
    private int spotSize ;
    /** 手柄X位置*/
    private float spotX;
    /** 关闭时内部灰色带高度*/
    private float offLineWidth;
    /** */
    private RectF rect = new RectF();
    /** 默认使用动画*/
    private boolean defaultAnimate = true;

    private OnSwitchChanged listener;

    //...
}

2.初始化与读取 

  读取自定义属性并赋值。讲了又讲的东西,略。

3.测量onMeasure与布局onLayout

  在onMeasure方法中根据给定mode和size来限定View,如果高宽不为明确值(UNSPECIFIED/AT_MOST),则定义自身高宽为明确值。 关于MeasureSpec的详细讲解,这里附上爱哥的一篇文章–MeasureSpec,深入到赋值读取的内部,不妨试着深入研究下。当然,更直接的方法就是点开源码一探究竟咯。
  onLayout方法中取得view的实际高宽,计算出圆角矩形半径,圆钮半径以及起始点x方向位置。还有On矩形和off矩形的宽度。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        /**
        *如果高宽未指定,则使用内置高宽明确大小
        */
        Resources r = Resources.getSystem();
        if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){
            widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, r.getDisplayMetrics());
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
        }

        if(heightMode == MeasureSpec.UNSPECIFIED || heightSize == MeasureSpec.AT_MOST){
            heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        final int width = getWidth();
        final int height = getHeight();

        /**
        *测量相应大小
        */
        radius = Math.min(width, height) * 0.5f;
        centerY = radius;
        startX = radius;
        endX = width - radius;
        spotMinX = startX + borderWidth;
        spotMaxX = endX - borderWidth;
        spotSize = height - 4 * borderWidth;
        spotX = toggleOn ? spotMaxX : spotMinX;
        offLineWidth = 0;
    }

  前三步完成基本赋值之后,开始设置和绑定相应事件。这里不作为重点部分也省略,主要讲一下绘制过程和核心控制逻辑。
    

4.绘制过程

  按照前面的简易示例图来绘制我们的ui图。

@Override
    public void draw(Canvas canvas) {
        //绘制on背景
        rect.set(0, 0, getWidth(), getHeight());
        paint.setColor(borderColor);
        canvas.drawRoundRect(rect, radius, radius, paint);

        //绘制off背景(缩放至0时候不绘制)
        if(offLineWidth > 0){
            final float cy = offLineWidth * 0.5f;
            rect.set(spotX - cy, centerY - cy, endX + cy, centerY + cy);
            paint.setColor(offColor);
            canvas.drawRoundRect(rect, cy, cy, paint);
        }

        //绘制圆钮轮廓border
        rect.set(spotX - 1 - radius, centerY - radius, spotX + 1.1f + radius, centerY + radius);
        paint.setColor(borderColor);
        canvas.drawRoundRect(rect, radius, radius, paint);

        //绘制圆钮
        final float spotR = spotSize * 0.5f;
        rect.set(spotX - spotR, centerY - spotR, spotX + spotR, centerY + spotR);
        paint.setColor(spotColor);
        canvas.drawRoundRect(rect, spotR, spotR, paint);

    }

  及诶按来便是我们的状态切换动画控制逻辑,即点击按钮之后setToggleOn或者setToggleOff执行的相应动作。

4.状态切换动画效果

    /**
    * 执行效果,如果animate为true表示有动画效果
    * 否则直接执行计算并显示最终打开"1"或者关闭"0"的效果绘制
    */
    private void takeEffect(boolean animate) {
        if(animate){
            slide();
        }else{
            calculateEffect(toggleOn ? 1 : 0);
        }
    }

    /**
    *这里偷个懒,直接使用空的animation,根据当前interpolatedTime(0~1)渐变过程来绘制不同阶段的View,达到动画效果
    *当然,也可以开启个线程或者定时任务,来实现从0到1的变换,劲儿改变视图绘制过程
    */
    private void slide(){
            Animation animation = new Animation() {
                @Override
                protected void applyTransformation(float interpolatedTime,
                        Transformation t) {
                    if(toggleOn){
                        calculateEffect(interpolatedTime);
                    }else{
                        calculateEffect(1-interpolatedTime);
                    }
                }
            };
            animation.setDuration(200);
            clearAnimation();
            startAnimation(animation);
    }

    /**
    *计算绘制位置
    *mapValueFromRangeToRange方法计算从当前位置相对于目标位置所对应的值
    *通过颜色变化来达到透明度动画效果(颜色渐变)
    */
    private void calculateEffect(final double value) {
        final float mapToggleX = (float) mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);
        spotX = mapToggleX;

        float mapOffLineWidth = (float) mapValueFromRangeToRange(1 - value, 0, 1, 10, spotSize);

        offLineWidth = mapOffLineWidth;

        final int fb = Color.blue(onColor);
        final int fr = Color.red(onColor);
        final int fg = Color.green(onColor);

        final int tb = Color.blue(offBorderColor);
        final int tr = Color.red(offBorderColor);
        final int tg = Color.green(offBorderColor);

        int sb = (int) mapValueFromRangeToRange(1 - value, 0, 1, fb, tb);
        int sr = (int) mapValueFromRangeToRange(1 - value, 0, 1, fr, tr);
        int sg = (int) mapValueFromRangeToRange(1 - value, 0, 1, fg, tg);

        sb = clamp(sb, 0, 255);
        sr = clamp(sr, 0, 255);
        sg = clamp(sg, 0, 255);

        borderColor = Color.rgb(sr, sg, sb);

        postInvalidate();
    }

  以上就是自定义View绘制的核心代码,详细查看源码SwitchButton。相较于组合方法,它更便捷,也有更高的灵活性和扩展性。同时还不需要图片资源支持。


3.SegmentControl样式实现

  常见的Tab有很多种,这里使用的是IOS常见的一种切换效果SegmentControl。本篇只用最简单的拼装View实现类似效果。有兴趣的可以自己尝试绘制达到更优效果。(有空的话也会在后边放出)

  • 通过view组合生成 最近单的方案,没有之一。使用现成的selector和背景来控制显示效果。各个子view分别继承 RelativeLayout并实现OnClick接口。最后在Segment中控制显示和点击切换。
  • 自定义View绘制生成 这里只是提供思路。定义一个ItemView,根据在Segment中位置挥之不同效果。背景效果会用selector.xml的都知道,使用shape标签产生的drawable对象,其实就是一个GradientDrawable。所以我们自定义view可以直接通过使用GradientDrawable的setCornerRadii(float[] radii) 来绘制同样的背景效果,劲儿可以做到不同颜色。最后,使用一个ViewGroup不含这些item即可。通过click事件来切换tab就可以了。

3.1 组合View实现

  首先,类似的定义一个可点击的通用的RelativLayout。(实现 Checkable接口使其可被选中也移除选中状态,详细可以参考前面的博文 微博/动态 点赞效果)。这里涉及三个新内容,稍微说明讲解下。
  
- checkMode 选中模式,是单选 CHECKMODE_CHECK 还是 CHECKMODE_RADIO 单选效果。使我们的自定义RelativeLayout可以做到单选和复选。
- onInitializeAccessibilityEvent 添加View接受事件源信息。即订阅checked事件。由于事件可能由内部子view点击触发,所以这里应该接收并处理相应的checked事件。当然,使用该方法首先要重写onInitializeAccessibilityNodeInfo方法,添加我们关注的状态信息。
- SavedState状态保存 当我们内部可能嵌套复杂view的时候,为了防止数据状态丢失,一般需要定义状态保存类,用以保存和恢复当前View状态。

#### 1.可点击的通用RelativeLayout

  • 继承实现Clickable接口,简要略过。

    //定义checked状态
    public static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };

    //重写SetChecked方法和isChecked方法略

    /**
    *根据当前选择模式checkMode 来控制单复选
    */
    @Override
    public boolean performClick() {
        if (checkMode == CHECKMODE_CHECK) {
            toggle();
        } else if (checkMode == CHECKMODE_RADIO) {
            setChecked(true);
        }
        return super.performClick();
    }

    /**
    *添加Drawable 的checked状态 ,并再绘制view是绘制相应状态效果
    */
    @Override
    public int[] onCreateDrawableState(int extraSpace) {
        int[] states = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(states, CHECKED_STATE_SET);
        }
        return states;
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        Drawable drawable = getBackground();
        if (drawable != null) {
            int[] myDrawableState = getDrawableState();
            drawable.setState(myDrawableState);
            invalidate();
        }
    }
  • 接受checked状态事件信息
    @Override
    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(CheckedRelativeLayout.class.getName());
        event.setChecked(checked);
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(CheckedRelativeLayout.class.getName());
        info.setCheckable(true);
        info.setChecked(checked);
    }
  • 保存View状态和恢复
      View自身重写保存和恢复的方法
[email protected]
    public Parcelable onSaveInstanceState() {//保存
        Parcelable superState = super.onSaveInstanceState();

        SavedState ss = new SavedState(superState);

        ss.checked = isChecked();
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {//恢复
        SavedState ss = (SavedState) state;

        super.onRestoreInstanceState(ss.getSuperState());
        setChecked(ss.checked);
        requestLayout();
    }

  用于保存数据的基本状态类型

static class SavedState extends BaseSavedState {
        boolean checked;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            checked = (Boolean) in.readValue(null);
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeValue(checked);
        }

        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

        @Override
        public String toString() {
            return "CompoundButton.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " checked=" + checked + "}";
        }

2.控制tab切换的SegmentView

  代码比较易于理解,这里直接贴出来查阅即可。
  基本思路,水平线性布局包裹对应左中右不同item个数的选项,并通过设置对应left/right/center来设置背景。然后分别为每个Item设置同一个点击事件,点击之后检查是否当前item被选中,改变statu,同时出发切换事件。详细代码:

public class SegmentView extends LinearLayout {

    protected final static int SEGMENT_LEFT_BG = R.drawable.segment_left_selector;
    protected final static int SEGMENT_CENTER_BG = R.drawable.segment_center_selector;
    protected final static int SEGMENT_RIGHT_BG = R.drawable.segment_right_selector;

    protected int leftBg = SEGMENT_LEFT_BG;
    protected int centerBg = SEGMENT_CENTER_BG;
    protected int rightBg = SEGMENT_RIGHT_BG;

    protected CheckedRelativeLayout2[] checkedRelativeLayouts;
    protected int index = -1;
    protected float textSize = -1;
    protected int textColorN = Color.BLACK, textColorP = Color.BLACK;
    protected OnIndexChangedListener onIndexChangedListener;

    public SegmentView(Context context) {
        super(context);
        initialize();
    }

    public SegmentView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
        initFromAttributes(context, attrs);
    }

    public SegmentView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize();
        initFromAttributes(context, attrs);
    }

    protected void initialize() {
        setGravity(Gravity.CENTER);
    }

    protected void initFromAttributes(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SegmentView);
        String content = a.getString(R.styleable.SegmentView_content);
        index = a.getInt(R.styleable.SegmentView_index, index);
        textSize = a.getDimension(R.styleable.SegmentView_textSize, textSize);
        textColorN = a.getColor(R.styleable.SegmentView_textColorN, textColorN);
        textColorP = a.getColor(R.styleable.SegmentView_textColorP, textColorP);
        leftBg = a.getResourceId(R.styleable.SegmentView_leftBg, leftBg);
        centerBg = a.getResourceId(R.styleable.SegmentView_centerBg, centerBg);
        rightBg = a.getResourceId(R.styleable.SegmentView_rightBg, rightBg);
        a.recycle();

        if (!TextUtils.isEmpty(content)) {
            String[] contentStrings = content.split(",");
            setContent(contentStrings);
        }
        setIndex(index);
    }

    public void setContent(String... content) {
        View[] views = new View[content.length];
        for (int i = 0, len = content.length; i < len; i++) {
            String s = content[i];
            TextView tv = new TextView(getContext());
            tv.setTextColor(textColorN);
            tv.setText(s);
            if (textSize != -1) {
                tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
            }
            views[i] = tv;
        }
        setContent(views);
    }

    public void setContent(View... content) {
        removeAllViews();
        int lastIndex = content.length - 1;
        checkedRelativeLayouts = new CheckedRelativeLayout2[content.length];
        checkedRelativeLayouts[0] = createLeftView(content[0]);
        checkedRelativeLayouts[lastIndex] = createRightView(content[lastIndex]);
        for (int i = 1; i < lastIndex; i++) {
            checkedRelativeLayouts[i] = createCenterView(content[i]);
        }
        for (View view : checkedRelativeLayouts) {
            LayoutParams llp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
            llp.weight = 1;
            addView(view, llp);
        }
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int i) {
        if (i < 0)
            return;
        checkedRelativeLayouts[i].setChecked(true);
    }

    public void setTextColorN(int textColorN) {
        this.textColorN = textColorN;
    }

    public void setTextColorP(int textColorP) {
        this.textColorP = textColorP;
    }

    protected CheckedRelativeLayout.OnCheckedChangeListener checkedChangeListener = new CheckedRelativeLayout.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CheckedRelativeLayout layout, boolean isChecked) {
            if (isChecked) {
                for (CheckedRelativeLayout2 item : checkedRelativeLayouts) {
                    if (!item.equals(layout)) {
                        item.setChecked(false);
                    }
                }
                if (onIndexChangedListener != null) {
                    int i = indexOf(checkedRelativeLayouts, layout);
                    index = i;
                    if (onIndexChangedListener != null) {
                        onIndexChangedListener.onChanged(SegmentView.this, index);
                    }
                }
            }
        }
    };

    protected CheckedRelativeLayout2 createLeftView(View contentView) {
        CheckedRelativeLayout2 layout = new CheckedRelativeLayout2(getContext());
        layout.setBackgroundResource(leftBg);
        layout.setGravity(Gravity.CENTER);
        layout.addView(contentView);
        layout.setOnCheckedChangeListener(checkedChangeListener);
        return layout;
    }

    protected CheckedRelativeLayout2 createCenterView(View contentView) {
        CheckedRelativeLayout2 layout = new CheckedRelativeLayout2(getContext());
        layout.setBackgroundResource(centerBg);
        layout.setGravity(Gravity.CENTER);
        layout.addView(contentView);
        layout.setOnCheckedChangeListener(checkedChangeListener);
        return layout;
    }

    protected CheckedRelativeLayout2 createRightView(View contentView) {
        CheckedRelativeLayout2 layout = new CheckedRelativeLayout2(getContext());
        layout.setBackgroundResource(rightBg);
        layout.setGravity(Gravity.CENTER);
        layout.addView(contentView);
        layout.setOnCheckedChangeListener(checkedChangeListener);
        return layout;
    }

    public void setOnIndexChangedListener(OnIndexChangedListener l) {
        this.onIndexChangedListener = l;
    }

    protected class CheckedRelativeLayout2 extends CheckedRelativeLayout {

        protected TextView textView;

        public CheckedRelativeLayout2(Context context) {
            super(context);
        }

        @Override
        public void addView(View child) {
            super.addView(child);
            if (child instanceof TextView) {
                textView = (TextView) child;
            }
        }

        @Override
        public void setChecked(boolean checked) {
            super.setChecked(checked);
            if (textView != null) {
                if (checked) {
                    textView.setTextColor(textColorP);
                } else {
                    textView.setTextColor(textColorN);
                }
            }
        }
    }

    public static interface OnIndexChangedListener {
        public void onChanged(SegmentView view, int index);
    }

    public static <T> int indexOf(T[] array, T obj) {
        for (int i = 0, len = array.length; i < len; i++) {
            if (array[i].equals(obj))
                return i;
        }
        return -1;
    }
}

  该方法比较简陋,背景颜色定制性不高。即只能通过既定drawable北京来实现。不过,其实是可以通过selector来定义相关背景drawable的。不妨试一下。
  

3.2 自定义View实现

  本来此方法只是简单提及的一个想法而已,今天有空就一并写了。时间匆忙,代码稍微有些混乱,不过还是能起到一定示范效用的,这里也贴出来供大家参考。
  整体思路

  • 定义子item 设置其选中状态和字体/背景色。通过测量方法保证显示范围和字体大小,通过GradientDrawable绘制圆角背景,并画对应字体。
    • 定义Segment 继承自ViewGroup,读取自定义属性,根据文本内容添加子View。然后重写OnMeasure方法和OnLayout方法来测量和布局子View。最后添加点击事件,提供监听接口。

        代码如下:


import com.qiao.demo.R;
import com.qiao.demo.R.styleable;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;

public class SegmentView extends ViewGroup implements OnClickListener{
    private final float r = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());

    private int bgColor = 0xff0072c6;
    private int fgColor = Color.WHITE;
    private float mTextSize = 3f*r;
    private String []mText= {"item1","item2","item3"};

    private int checkedItem=1;
    private OnItemClickListener listener;

    public SegmentView(Context context) {
        super(context);
        initFromAttributes(context, null);
        initalize();
    }

    public SegmentView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initFromAttributes(context,attrs);
        initalize();
    }

    protected void initFromAttributes(Context context, AttributeSet attrs) {
        if(attrs==null) return;
        TypedArray a = context.obtainStyledAttributes(attrs,  R.styleable.SegmentView0);
        String content = a.getString(R.styleable.SegmentView0_content0);
        if(!isEmpty(content)){
            mText = content.split(",");
        }
        checkedItem = a.getInt(R.styleable.SegmentView0_index0, checkedItem);
        mTextSize = a.getDimension(R.styleable.SegmentView0_textSize0, mTextSize);
        bgColor = a.getColor(R.styleable.SegmentView0_bgColor, bgColor);
        fgColor = a.getColor(R.styleable.SegmentView0_textColor, fgColor);
        a.recycle();
    }

    public void initalize(){
        int length = mText.length;
        for(int i=0;i<length;i++){
            View view = new ItemView(getContext(),mText[i],getGravity(i,length),i==checkedItem);
            view.setOnClickListener(this);
            addView(view,LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int count = getChildCount();
        int childWidthMeasureSpec = widthMeasureSpec;
        int maxWidth = 0;
        int maxHeight = 0;
        if(widthSize>=0){
            maxWidth = widthSize/count;
            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth,widthMode);
        }

        for(int i=0;i<count;i++){
            View child = getChildAt(i);
            measureChild(child, childWidthMeasureSpec,heightMeasureSpec);
            maxWidth = Math.max(maxWidth,child.getMeasuredWidth());
            maxHeight = Math.max(maxHeight,child.getMeasuredHeight());
        }

        setMeasuredDimension(getDefaultSize(maxWidth*count, widthMeasureSpec),
                getDefaultSize(maxHeight, heightMeasureSpec));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if(!changed) return;
        int count = getChildCount();
        int left = 0;
        for(int i=0;i<count;i++){
            View child = getChildAt(i);
            child.layout(left,0,left+child.getMeasuredWidth(),child.getMeasuredHeight());
            left += child.getMeasuredWidth();
        }
    }

    private int getGravity(int i,int len){
        if(i==0){
            if(i==len-1)
                return ItemView.GRAVITY_SINGLE;
            else
                return ItemView.GRAVITY_LEFT;
        }else if(i==len-1){
            return ItemView.GRAVITY_RIGHT;
        }else
            return ItemView.GRAVITY_CENTER;
    }

    @Override
    public void onClick(View v) {
        int count  = getChildCount();
        for(int i=0;i<count;i++){
            View child = getChildAt(i);
            if(v.equals(child)){
                checkedItem = i;
                ((ItemView)child).setChecked(true);
            }else{
                ((ItemView)child).setChecked(false);
            }
            child.postInvalidate();
        }
        if(listener!=null){
            listener.onItemClick((ItemView)v, checkedItem);
        }
    }

    /**
     * segment子集item
     */
    class ItemView extends View{
        public final static int GRAVITY_SINGLE = 1<<0;
        public final static int GRAVITY_LEFT = 1<<1;
        public final static int GRAVITY_CENTER = 1<<2;
        public final static int GRAVITY_RIGHT = 1<<3;

        private GradientDrawable drawable;
        private int gravity = GRAVITY_SINGLE;
        private boolean isChecked;
        private String text;

        private Paint mTextPaint;
        private Rect mTextBound = new Rect();

        private ItemView(Context context,String text,int gravity,boolean isChecked){
            super(context);
            this.text = text;
            this.gravity = gravity;
            this.isChecked = isChecked;
            init();
        }

        private void init(){
            mTextPaint = new Paint();

            mTextPaint.setTextSize(mTextSize);
            mTextPaint.getTextBounds(text, 0, text.length(), mTextBound);

            drawable = new GradientDrawable();
            drawable.setStroke((int)(r/5), bgColor);
            setItemGravity(gravity);
            setChecked(isChecked);
        }

        public void setItemGravity(int gravity){
            this.gravity = gravity;
            switch (gravity){
                case GRAVITY_SINGLE:
                    drawable.setCornerRadii(new float[]{r,r,r,r,r,r,r,r});
                    break;
                case GRAVITY_LEFT:
                    drawable.setCornerRadii(new float[]{r,r,0,0,0,0,r,r});
                    break;
                case GRAVITY_CENTER:
                    drawable.setCornerRadii(new float[]{0,0,0,0,0,0,0,0});
                    break;
                case GRAVITY_RIGHT:
                    drawable.setCornerRadii(new float[]{0,0,r,r,r,r,0,0});
                    break;
            }
        }

        public void setChecked(boolean isChecked){
            this.isChecked = isChecked;
            mTextPaint.setColor(isChecked? fgColor:bgColor);
            drawable.setColor(isChecked? bgColor:fgColor);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);

            if(widthMode == MeasureSpec.AT_MOST){
                widthSize = mTextBound.width() + (int)(8*r);
                widthMode = MeasureSpec.EXACTLY;
            }
            if(heightMode == MeasureSpec.AT_MOST){
                heightSize = mTextBound.height() + (int)(4*r);
                heightMode = MeasureSpec.EXACTLY;
            }

            widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize,widthMode);
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize,heightMode);
            setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);

            int height = getMeasuredHeight();
            int width = getMeasuredWidth();
            if(height>=0){
                float textSize = Math.min(mTextSize,height-2*r);
                if(width>0){
                    textSize = Math.min(textSize,(width-2*r)*2/text.length()); //英文比中文短(中文为两个字符),故取mText.length()/2作为平均宽度
                }
                if(textSize != mTextSize ){
                    mTextPaint.setTextSize(textSize);
                    mTextPaint.getTextBounds(text, 0, text.length(), mTextBound);
                }
            }
        }

        @Override
        public void draw(Canvas canvas) {
            Rect rect = canvas.getClipBounds();
            drawable.setBounds(new Rect(rect));
            drawable.draw(canvas);
            int l = (rect.width() - mTextBound.width())/2;
            int b = (rect.height() + mTextBound.height())/2;
            canvas.drawText(text, l, b, mTextPaint);
        }

    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener){
        this.listener = onItemClickListener;
    }

    interface OnItemClickListener{
        void onItemClick(ItemView item,int checkedItem);
    }

    public static boolean isEmpty(String str){
        return null==str || str.trim().length() == 0;
    }
}

  参照前面两段讲述完全可以理解了。使用时候可以方便的通过自定义属性来控制字体颜色和点击背景。可以动态变更View高宽。有问题的同学可以在文末提出或指正。

3.总结

  感觉自己学习进步的速度很慢,常常伴随着焦急浮躁。这篇文章也是积累了好久才慢吞吞的写完了。代码方面,个人也有不少不良习惯,助事业不够清晰,不过总体上不是有碍观瞻吧。
  同样的东西,尝试用不同想法写两遍,我觉得是有好处的。至少于我,能看到不少有意思的东西。
  
  最后, 附上本文的 示例源码 . 由于资源上传较早,第二部分的自定义View并没有打包上传。不过上便已经贴出完整代码了,可以直接拿来使用。

  后边在考虑是写一写非UI层面的东西,还是继续写关于常见的增删改UI界面。待定,总之,fighting..


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章
  • UI-Android中的状态切换旋钮自定义 UI-Android中的状态切换旋钮自定义

    UI--Android中的状态切换按钮自定义 <代码里的世界> -UI篇 用文字札记描绘自己 android学习之路 转载请保留出处 by Qiao http://blog.csdn.net/qiaoidea/article/details/46715453 1.概述 Android中关于控制开关和页面/状态切换的使用场景还是比较多的.源生做的支持也有比如RadioGroup 和Tabhost等.这里准备通过自定义View来模仿学习下IOS两种常见UI样式: SwitchButton 和 Se

  • UI--Android中的状态切换按钮自定义 UI--Android中的状态切换按钮自定义

    1.概述 Android中关于控制开关和页面/状态切换的使用场景还是比较多的.源生做的支持也有比如RadioGroup 和Tabhost等.这里准备通过自定义View来模仿学习下IOS两种常见UI样式: SwitchButton 和 SegmentControl. 首先先通过简易的组装View来实现两种UI的相应效果,其次呢,尝试通过绘制来达到同样的更灵活的样式.代码前后共实现按钮切换和页面切换两个样式,三种实现方案,其中,两种SwitchButton实现,一种SegmentControl实现.

  • Android中兑现应用切换主题机制 Android中兑现应用切换主题机制

    Android中实现应用切换主题机制 文章出处:http://gundumw100.iteye.com/blog/1052260 一直很想弄清楚好多应用中是如何实现换皮肤这项功能的,花了下午点时间,查了下资料也实现了个切换主题的Demo; 首先要感谢下这位大哥,参阅了下他写的文件http://www.eoeandroid.com/forum-viewthread-tid-31756-highlight-%E7%9A%AE%E8%82%A4.html 好了,废话不多说了,该切换主题的demo里面一

  • Android中实现应用切换正题机制 Android中实现应用切换正题机制

    Android中实现应用切换主题机制 一直很想弄清楚好多应用中是如何实现换皮肤这项功能的,花了下午点时间,查了下资料也实现了个切换主题的Demo; 首先要感谢下这位大哥,参阅了下他写的文件http://www.eoeandroid.com/forum-viewthread-tid-31756-highlight-%E7%9A%AE%E8%82%A4.html 好了,废话不多说了,该切换主题的demo里面一共实现了两个功能,其一,搜索已经安装的皮肤,其二,应用安装的皮肤. 主项目包名为org.le

  • 应用和管理Android中Activity的切换动画(二)

    使用和管理Android中Activity的切换动画(二) 上一篇文章中讲到了如何使用overridePendingTransition设置Activity切换动画.这一篇文章中,我会通过设置Activity主体的形式设置Activity的切换动画. 我们知道,在Manifest文件中声明Activity时,可以通过android:theme属性设置Activity的主题.主题中定义了关于Activity外观的很多特性.同时,主题中还可以定义Activity的切换动画.通过主题的形式定义的Act

  • Android札记(七):Android中Activity的切换② Android札记(七):Android中Activity的切换②

    Android笔记(七):Android中Activity的切换② 上一节,我们通过对Activity的UI属性设置,完成了一个类似Activity切换的程序,可是大家都知道,明明只有一个Activity的子类存在于程序当中,谈何切换呢? 本节将从实质上去完成这个功能. 进入本文之前,我们先来看看一个类:android.content.Intent Intent是一种运行时绑定(runtime binding)机制,它能在程序运行的过程中连接两个不同的组件.通过Intent,你的程序可以向And

  • Android札记(六):Android中Activity的切换① Android札记(六):Android中Activity的切换①

    Android笔记(六):Android中Activity的切换① Activity是一个应用中的组件,它为用户提供一个可视的界面,方便用户操作,比如说拔打电话.照相.发邮件或者是浏览地图等.每个activity会提供一个可视的窗口,一般情况下这个窗口会覆盖整个屏幕,但在某此情况下也会出现一些比屏幕小的窗口飘浮在另外一相窗口上面.类比Windows当中的概念,Activity相应于一个Dialog(MFC)或者是Form(C#),它为用户提供一个可视的界面. 下面是摘自android API的,

  • Android替ViewPager增加切换动画——自定义ViewPager Android替ViewPager增加切换动画——自定义ViewPager

    Android为ViewPager增加切换动画--自定义ViewPager 转载请注明出处:http://blog.csdn.net/allen315410/article/details/44224517 在上篇博客中,我写了一个使用属性动画为ViewPager添加切换动画的方法,并且可以兼容到Android3.0以下版本的设备上,那么关于为ViewPager添加动画的方式还会有另外一种实现方案,就是自定义一个自己带动画效果的ViewPager,关于上篇博客,还没来得及查看的朋友可以点击这里进

  • Android中View怎么切换Focus Android中View怎么切换Focus

    Android中View如何切换Focus 视图(View)类代表了一种基本的用户界面组成模块.一个视图占据了屏幕上的一个矩形区域,并响应绘制图形和事件处理.视图类是窗体类(Widget)的基类,而窗体类用来生成可交互的用户图形接口(interactive GUI). 视图类的使用窗口中所有的视图构成一个树形结构.要想增加视图,既可以用直接添加代码的方法,也可以在一个或者多个XML文件中声明新视图构成的树.在视图类的子类中,有的可以用来控制,有的具有显示文字.图片或者其他内容的功能. 当视图树被

  • (转)Android LinearLayout依据状态切换图片(模拟按钮的效果)

    (转)Android LinearLayout根据状态切换图片(模拟按钮的效果) 在Android中Button可以根据选中,点击等状态切换图片,我想用LinearLayout实现类似的功能, 但是默认情况下pressed状态"容器类"(继承于ViewGroup的类)是接收不到的, 所以LinearLayout的按下就没有效果,后来分析代码,可以通过设置setAddStatesFromChildren 方法获得内部View的状态,就可以取得pressed状态. linearLayout

  • 应用和管理Android中Activity的切换动画(一)

    使用和管理Android中Activity的切换动画(一) 在Android2.0之后,当程序在Activity之间进行切换时,是可以添加切换动画的.其实添加Activity切换动画只是很简单的调用一个api函数:overridePendingTransition(int enterAnim, int outAnim),两个参数分别指向两个定义动画的xml文件资源,下面先用一段简单的代码说明该函数的使用方法. 假设有两个Activity,分别为A和B. 当在A中启动B时,可以使用如下方式添加动画

  • Android中Toast展示时间的自定义

    Android中Toast显示时间的自定义 Android中Toast的显示时间为特定时间且不可更改,但是有时候我们开发设计需要让Toast显示更长时间,或者自己完全控制Toast的显示和关闭.通过查看Toast类的源码,可以看出,这有点难为它了,Toast类本身并没有提供相应方法. 但是通过源码的查看,还是可以看出点眉头.源码分析思路在这里转eoe里的一篇文章,思路较为清晰: 转: Toast信息提示框之所以在显示一定时间后会自动关闭,是因为在系统中有一个Toast队列.系统会依次从队列中取(

  • android中的状态封存

    android中的状态保存 package com.zzl.call; import android.app.Activity; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.os.Bundle; import android.widget.Toast; /** * Activity状态的保存 * * * 第一,在onSaveInstanceState函数中进行数据

  • Android中横竖屏切换加载不同布局导致fragment数据丢失的有关问题 Android中横竖屏切换加载不同布局导致fragment数据丢失的有关问题

    Android中横竖屏切换加载不同布局导致fragment数据丢失的问题 亲们好,求助大神,改了好长时间也没搞定这个问题,图上的四幅图依次为竖屏切横屏,再次切竖屏,再切横屏的效果,当再次切换竖屏的时候数据就没有显示了.我设置了android:configChanges="screenSize|orientation|keyboardHidden",所以在横竖切换的时候只会执行 @Override public void onConfigurationChanged(Configurat

  • Android中怎么控制LogCat的自定义输出 Android中怎么控制LogCat的自定义输出

    Android中如何控制LogCat的自定义输出 在Android开发中,LogCat是一个非常重要的调试工具,可以输出很多关于项目或者手机的信息.但是正是由于LogCat功能的过于强大,输出的信息量也是极为庞大的,那么我们就需要通过一定的方式根据我们的需要限定LogCat的输出,这样才能使LogCat帮我们起到更好的调试代码的作用. LogCat输出的类型一般有五种,分别是verbose,debug,info,warn,error.其中verbose的最为宽泛,如果选择verbose,那么就会

  • Android菜鸟的成长笔记(15)—— Android中的状态保存探究(下) Android菜鸟的成长笔记(15)—— Android中的状态保存探究(下)

    在上一篇中我们简单了解关于Android中状态保存的过程和原理,这一篇中我们来看一下在系统配置改变的情况下保存数据及恢复数据的过程. 下面我们先来看一个现象:(代码在 Android中状态保存探究(上)中) 先启动应用如下: 打印的Log 再翻转屏幕vcD48cD48aW1nIHNyYz0="http://www.2cto.com/uploadfile/Collfiles/20140225/20140225085413238.jpg" alt="\" /> 打

  • Android中实现Bit地图在自定义View中的放大与拖动 Android中实现Bit地图在自定义View中的放大与拖动

    Android中实现Bitmap在自定义View中的放大与拖动 一基本实现思路: 基于View类实现自定义View –MyImageView类.在使用View的Activity类中完成OnTouchListener接口,实现对MotionEvent事件的监听与处理,常见的MotionEvent事件如下: ACTION_DOWN事件,记录平移开始点 ACTION_UP事件,结束平移事件处理 ACTION_MOVE事件,记录平移点,计算与开始点距离,实现Bitmap平移,在多点触控时候,计算两点之间

  • Android中的状态保存-SharedPreferences和Bundle(文末小彩蛋)

    SharedPreferences 是使用键值对的方式来存储数据的.也就是说当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来.而且SharedPreferences 还支持多种不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的数据也是整型的,存储的数据是一个字符串,读取出来的数据仍然是字符串.这样你应该就能明显地感觉到,使用SharedPreferences 来进行数据持久化要比使用文件方便很多,下面我们就来看一下它的具体用法吧

  • 状态切换旋钮,功能类似UISwitch

    状态切换按钮,功能类似UISwitch 创建按钮 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(10.0, 10.0, 100.0, 40.0); [button setTitle:@"Normal" forState:UIControlStateNormal]; UIImage *image = [UIImage imageNamed:@"

  • android中junit测试各种旋钮事件

    android中junit测试各种按钮事件 public class CalculatorHitSomeButtons extends ActivityInstrumentationTestCase <Calculator>{ public boolean setup = false; private static final String TAG = "CalculatorTests"; Calculator mActivity = null; Instrumentati

最新文章
  • mac mini 接 60 寸 4K 家用电视可行性

    去年买的海尔阿里电视, 60 寸 4K 分辨率,最近突然想入一个 mac mini ,用途是看电影,上网看资讯,收发邮件. 是否可行呢?有说外接电视后,文字显示不忍直视,看高清电影会卡顿和模糊. 请有经验的大牛指导下~~ --cut-- badec在2016-05-08 09:32:32回答到: 看高清电源卡不至于,但是看网页就别指望了.调大字体就没有接 60 寸 4k 的必要了

  • 蒙板、通道、选区的本质和联系

    蒙板.通道.选区,这是PS中最容易让人产生困惑的三个概念.蒙板的种类繁多,如图层蒙板.矢量蒙板.剪贴蒙板.快速蒙板;制作选区的手段也是琳琅满目,让人应接不暇;通道的概念又极尽晦涩,让人不知所云,恰似一头雾水.在对这三个概念的理解上,可谓众说纷云,莫衷一是.本帖旨在探究三者的内涵实质及其内在联系,以期拨开迷雾,明辨是非,成蒙板.通道.选区之大统. 需要说明两点:一是本帖不是告诉大家如何使用蒙板.通道和选区的教程,而是试图从理论上阐释三者的内涵实质,并进一步探究三者之间的内在联系,因此,本帖属于理论

  • CF军火基地倒计时三天 CF免费武器领取地址 CF军火基地倒计时三天 CF免费武器领取地址

    <穿越火线>中军火基地基本上每月都会出现,而且奖励还不重复,我们一起来看看本月军火基地的奖励都是什么吧. 活动地址:点此进入 签到日历 兑换专区 抽奖专区 活动规则 1首先绑定游戏大区: 2玩家每月都可在页面领取见面抽奖: 3玩家每日在月券系统页面签到可获得1张月券,每月首次签到可获得10月券: 4玩家每日完成经验值任务可获得对应奖励的月券,月券可在绑定的大区内兑换道具: 5每月玩家完成任务成就,可一次性获得对应月券 6每个QQ号每天仅限2次兑换,且必须在绑定的大区内兑换道具: 710张月券抽

  • 原谅我歌词《还是夫妻》电视剧片头曲 原谅我歌词《还是夫妻》电视剧片头曲

    原谅我 - 刘德华 <我的特工爷爷>电影主题曲 词:刘德华/易桀齐 曲:伍冠谚/易桀齐 编曲:蔡晓恩 原谅我 你原谅的不是我 等宽恕在你的口中 仿佛变得好难得 哭着说 你说你会原谅我 你到底只是随口说说 还不是真的 都忘了 我是我 人活着 又如何 多么痛 的结果 不能解脱 选择沉默 我记得 你说过你爱我 你却说 只是我想多了 没有你 没有我 没有了 所有生活 我记得 你说过你爱我 有些事 想回忆 却忘了 记忆里 删了你 删了我 剩下什么 只剩下一场梦 原谅我 你原谅的不是我 等宽恕在你的口中

  • 网易公开课讲师亲述“坠机亲历” 网易公开课讲师亲述“坠机亲历”

    3月份马航MH370失联的阴影尚未走出,近日连续三起飞机事故震惊世界.在30000英尺高空对生命失去自主权的感觉让每个人深陷恐慌,2014年7月,也因之成为人类航空史上最黑暗的一个月. 网易公开课TED课程之<坠机让我学到的三件事>,Ric Elias为大家讲述他自己的亲身经历和感悟.2009年一月,当机号1549 飞机迫降纽约哈德逊河,Ric Elias正坐在第一排.在坠机的当下,他的心中在想什么? 在网易公开课的TED课程中,他第一次在公开场合说出他的故事. 在课程中,Ric Elias讲

  • Apple Watch怎么预定? Apple Watch怎么预定?

    Apple Watch怎么预定购买? 根据苹果官网上放出的Apple Watch预购指南表示,预购及取货渠道分为两种: 1.通过Apple Store在线商店预购Apple Watch,苹果将通过快递将手表送货上门.支付方式支持借记卡.信用卡以及支付宝,其中使用VISA或万事达信用卡付款不需跳转其他支付网站,支付过程最快. 2.在苹果官网预约,再前往预约时选择的苹果零售店付款取货.网上预约过程可能会需要手机号.身份证号码和Apple ID,如果您是苹果产品的用户,并曾注册过Apple ID,则可

  • 《LOL》设计师:盖伦将继续改动 黑切调整分析 《LOL》设计师:盖伦将继续改动 黑切调整分析

    在昨日的测试服改动中,黑切与兰顿基本可以说进行了大规模的重做,而黑切提供的高额生命值与减CD,再加上两个唯一被动成为了玩家们津津乐道的话题,对此设计也有自己的看法. 首席设计师与你聊盖伦 在5.7版本当中,盖伦的大招得到了一定的加强,首席设计师Meddler在论坛上表示未来设计团队想要对盖伦进行更多的改动: "将来我们希望对盖伦进行更多的改动,特别是让他拥有更多的游戏选择,这样他就不必在团战中朝着某个家伙跑过去把所有的技能都用在他身上.请大家抱有适当的预期,有可能其中会夹杂对这个英雄机能的调整,

  • 如何给PowerPoint演示文稿加密 如何给PowerPoint演示文稿加密

    1.单击"office"按钮,在弹出的下拉框中选择"准备"-"加密文档"命令. 2.在弹出的"加密文档"对话框中,输入密码,单击"确定"按钮. 3.弹出"确认密码"对话框,在"重新输入密码"文本框中再次输入刚刚设置的密码,单击"确定"按钮即可完成加密. 4.关闭该演示文稿,当我们重新打开演示文稿时,会弹出"密码"对话框,这时需要

  • 耵聍是耳道的清洁剂 耵聍是耳道的清洁剂

    我们的身体真是一部神奇的机器!想当年地位及其地下的耵聍,人们都想尽办法要除掉它.后来我们知道了,耵聍虽小,功劳却不小,不能随意地清除它.耵聍是由中耳腺体分泌物和角化脱落的皮肤细胞混合而成,另外还有少数的毛发参与其中.下面本站小编就来给大家讲解一下. 耵聍是耳道的清洁剂,同时也是敏感的耳道的润滑剂. 正常情况下,如果外耳道中聚集了过多的叮咛,它就会自动脱落,即使你没有观察到.活动下巴有助于耵聍的脱落. 所以医生们都建议大家不要为了使耵聍脱落而故意去掏耳朵. 但是,正如我们的祖先所言,物极必反.任何

  • 肉鸽是滋补妇女的佳品

    据营养学家测定,肉鸽肉含蛋白质(蛋白质食品)24.49%,超过牛.羊.鸡.鸭.猪等肉类食品,而脂肪仅占0.73%,又低于其他肉类,故被誉为 "高级营养佳品". 据科学家.医学家和营养学家几十年的研究与测定,认为肉鸽肉具有补血(补血食品).活血作用,经常食用可防治血管硬化.高血压(血压食品).气喘等多种疾病.另外,它又富含维生素(维生素食品)B族,对于毛发脱落.早白.中年(中年食品)早秃及贫血等疾病,均有较好的治疗作用. 同时,对妇女产后(产后食品)失血和外伤流血等患者,食用后有滋补作用

热门推荐