位置:首页 » 技术 » 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

最新文章
  • 给维基百科一点点帮助 给维基百科一点点帮助

    今天在用维基查资料的时候,看到了这个,维基百科是最敬佩的公司之一: 立马给捐了20块钱,虽然钱不多,但是也算尽了绵薄之力, 要是每个看维基百科的人, 都捐这20块的话,相信维基百科就不用筹款了. 希望有心的V友帮助下维基百科,先谢谢你了. --cut-- line在2016-05-09 00:28:50回答到: 刚捐了3$. tking4在2016-05-09 00:28:50回答到: @line jamesfjx在2016-05-09 00:28:50回答到: 每年捐3刀,毕竟帮我涨了不少姿势

  • 车展频繁被“复制” 乐视体育超级自行车欲亮剑维权 车展频繁被“复制” 乐视体育超级自行车欲亮剑维权

    随着乐视超级自行车在去年一飞冲天,5月9日刚刚结束的中国国际自行车展览会上,智能自行车成为全场主角.众多厂商推出相关创新产品的同时,一些"伪创新"也鱼目混珠.乐视体育发现,乐视超级自行车被多家企业"复制"后堂而皇之地出现在展会现场.乐视体育方面表示,已经做好侵权取证,将利用法律武器保护自身知识产权. 以鸟类俯冲姿态为灵感设计出的全新车架造型是乐视体育超级自行车的标志之一,也成为其得名"鵟"的由来,早在自行车正式面世前即已进行了多组专利申请,以保护

  • HTML语法效果 HTML语法效果

    一. 基本语句: 1. 发各种字体的字: 黑体字 : <font face="黑体">黑体字</font> 宋体字 : <font face="宋体">宋体字</font> 仿宋字 : <font face="仿宋_GB2312">仿宋字</font> 楷体字 : <font face="楷体_GB2312">楷体字</font>

  • 高档办公室隐藏的无形杀手有哪些 高档办公室隐藏的无形杀手有哪些

    在众多写字楼里,除了一眼望过去可以看到办公的格局之外,更多的还有你看不到的潜藏的健康威胁.这些光鲜靓丽的办公室格局里看着光鲜亮丽的办公格局到处都有可能潜藏污染,这些"无形杀手"会带来严重的健康隐患和问题.现在,让我们看看一个高档办公室里可能隐藏的无形杀手吧让你打起十二分精神来防备. 1. 空调 使用空调的房间大多是密闭的,就使室内与室外的空气流通几乎为零,这样不通风的时间超过1个小时,室内空气就会出现质量差.氧气相对不足.二氧化碳超标等问题.多数写字楼用的都是中央空调,很多中央空调还需

  • 7个香体用品小窍门 7个香体用品小窍门

    1.喷雾类会比体香膏清爽,也比较适合大面积使用. 2.搽在适合搽的地方.基本上,除了脸部之外的地方都可以.一般人会抹在腋下.耳后.手腕间.大腿内侧.脖子等地方.量的话,大概来回抹1-2次即可. 3.在洗澡后.出门前身体干爽的时候擦,如果身上已经流汗,那么要先把汗擦干净,再涂上体香膏.擦上后等到全干再穿上衣服,尤其乳液状较难干,要耐心等一下. 4.使用体香膏时,只要出门前使用一次即可,中间千万不用再补. 5.使用后,回家一定要清洁身体,洗澡时要洗干净,避免对皮肤不好. 6.如果已擦香水,就不要再用

  • HTC Butterfly获Android 4.2.2及Sense 5更新 HTC Butterfly获Android 4.2.2及Sense 5更新

    在发布了 HTC Butterfly S 后,HTC 也不忘为上一代的旗舰手机 HTC Butterfly 发放更新,让手机不致成为第二部的 HTC One S. 现在,HTC 宣布将会先在台湾地区为 HTC Butterfly 的用户推送 Android 4.2.2 Jelly Bean 系统及 HTC Sense 5 的更新,更新同时包括了 HTC One 上的 BlinkFeed 功能,更新后大家便可以在 HTC Butterfly 上使用深受好评的 BlinkFeed 功能了.

  • 尊老爱幼的名言精选 尊老爱幼的名言精选

    尊老爱幼的名言精选 1.你不同情跌倒在地的老人,在你摔跤时也没有人来扶助.--印度谚语 2.要知亲恩,看你儿郎;要求子顺,先孝爹娘.--<四言> 3.将出牵衣送,未归倚阁望.--黄遵宪 4.对父母养育之恩的报答,也是对人类劳动的尊重.--俗语 5.挟泰山以超北海,此不能也,非不为也;为老人折枝,是不为也,非不能也.--庄子 本站阅读配图 6.老来受尊敬,是人类精神最美好的一种特权.--司汤达 7.老年时像青年一样高高兴兴吧!青年,好比百灵鸟,有它的晨歌;老年,好比夜莺,应该有他的夜曲.--康德

  • Linux文件实时同步,可实现一对多

    Linux文件实时同步,可实现一对多 说明:该功能服务端安装sersync2,客户端安装rsync,原理就是服务端主动推送设定目录下的所有更新的文件到各个客户端rsync接收. rsync大家都知道,是Linux自带的数据同步工具,而sersync2是google大神的开源项目http://code.google.com/p/sersync/ 下面给出具体的实现步骤,实现的详细原理大家可以去上面的开源网址,上面说的很详细 客户端配置,首先系统安装rsync工具, [php] [[email protected]

  • 发一个在前辈基础上修改的下拉时间代码(2个选择时间,到小时),好的话给顶一下,该怎么处理

    发一个在前辈基础上修改的下拉时间代码(2个选择时间,到小时),好的话给顶一下 <html> <head> <script language= "JavaScript "> function initDate() { var obj=document.all[ 'dateform ']; var y=obj.year; var m=obj.month; var d=obj.date; var h=obj.hour; var y1=obj.year1;

  • 自娱自乐——flex4自持简易mp3播放器v1.0 自娱自乐——flex4自持简易mp3播放器v1.0

    自娱自乐--flex4自制简易mp3播放器v1.0 刚开始学flex4没多久,练练手,做了个flex4的简易MP3播放器,自娱自乐而已呵呵. 希望大家多多指点一下. 感觉flex4与以前的版本相比变化较大,而且很多教程都还是flex3的,所以在制作的时候还是遇到了些麻烦的事情. 此程序为AIR工程,使用Flash Buidler 4 ,制作,新建项目时使用AIR项目. 文件结构: music.swf 主运行文件 musiclist.xml 歌曲列表 music文件夹 存放歌曲的地方 在运行本程序

热门推荐
  • 你有没有这样一个异性知己 你有没有这样一个异性知己 我是个女人,我的异性朋友当然是男人.而恰恰我比较幸运的是,我有那么一两个非常铁的异性朋友,用男人的话说叫哥们,他们总是在你的身边,陪你哭陪你笑,给你最宽阔的安慰.从我自身感受来说,有这样的朋友,是可遇不可求,更是人生之一幸事. 有这样的朋友,你从来都不会觉得伤心时孤独无助.失恋时,在他们面前哭个肆无忌惮,任它花容尽失也无所谓,然后接过他递来的纸巾,在他痛惜的眼神中再次放声大哭;心情不好时,找一件八竿打不到的事情向他发火一通,然后看见他贼贼的笑脸,心里腾地就温暖起来,这个人,就是能在你发火时笑出来
  • 手机版米聊如何添加好友 手机版米聊如何添加好友 1.米聊提供搜索.附近的人.邀请好友.握手.扫描二维码.晒名片六种添加好友的方式.同时系统会根据现有的好友给您推荐一些您可能认识的朋友. 2.在搜索界面,可以通过搜索对方的"姓名"."米聊号"."学校"."公司"等来查找好友. 3.通过查看附近的人,可以迅速发现身边的好友,查看时可以选择查看附近所有人.查看附近的男生.查看附近女生,分类明确,添加好友更方便. 4.握手可以帮您找到和您同时握手的朋友,并且可以批量添加好友,十分方
  • QQ空间黑色非主流大图模块:谁为我落泪 QQ空间黑色非主流大图模块:谁为我落泪 使用方法:1:点击自定义.2:点击右侧的新建模块→点击图片模块.3:模块名称随便,用一个空格最好,图片地址上输入图片的地址,再点击"更多设置",背景.边框选择无,确定保存. 下面是图片的缩略图,点击查看QQ空间大图模块: 第二款: 第三款: 第四款:
  • 刀塔传奇沉默怎么打 沉默现阶段情况解析 刀塔传奇沉默怎么打 沉默现阶段情况解析 在刀塔传奇这款游戏里面沉默怎么打呢,沉默现阶段情况怎么样?今天小编就给大家介绍一下刀塔传奇沉默怎么打 沉默现阶段情况解析,下面跟小编一起来看看吧! 给各位刀塔传奇的玩家们分享一下刀塔传奇沉默怎么打 沉默现阶段情况解析. 刀塔传奇沉默现阶段情况分析,在我看来永远不会过时的英雄,你不能缺少的英雄,其实说白了,这个游戏就两类英雄,物理和法系,只要有法系那么就会有沉默的位置,何况物理英雄中何其多大招也能被沉默所控制. 大招不多说,小技能输出其实不差,能量流失和小沉默可以有效克制多前排,法球单点伤害真心不