澳门新葡萄京娱乐场 23

澳门新葡萄京娱乐场Android自定义控件

1. 对话保持的解决方案。

要求:

1、app中使用webview访问具体网站的内容,但是app与服务器的沟通是使用HttpURLConnection来完成。
2、webview访问时不需要再次登陆,继承app的登陆状态。

会话未保持的现象:

澳门新葡萄京娱乐场 1

1、虽然app已经登录服务器,但是在webview中还是提示需要登录。

2、app下一次对服务器的请求也会失败,提示session过期。

解决方案:

1、获取到HttpUrlConnection中服务器返回的session id。
2、本地保存session id,每次对服务器的请求,手动添加。
3、将此session id设置到持有webview的activity中的CookieManager里。

关键代码:

网络处理类  NetHelper

/**
     * 发送登陆请求,并将SESSIONID保存起来
     * @param urlPath 登陆请求的地址
     * @return 返回的内容
     * */
public static String login(String urlPath)  {

        ......省略号......

        try {
            URL url = new URL(urlPath);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            //设置请求方式
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
//            conn.setReadTimeout(5000);

            int responseCode = conn.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                InputStream is = conn.getInputStream();
                cookList = conn.getHeaderFields().get("Set-Cookie");
                if ((sessionId == null) && (cookList != null)) {
                    for (String value : cookList) {
                        if ((value != null) && (value.toUpperCase().indexOf(";") > 0)) {
                            sessionId = value.split(";")[0];
                        }
                    }
                }

            ......省略号......

}
        }catch (Exception e){
            e.printStackTrace();
}
......省略号......
    }/**
     * 发送一条请求,将内容以字符串返回
     * @param urlPath 请求的地址
     * @return 返回的内容
     * */
public static String request(String urlPath) {

        ......省略号......

        try {
            URL url = new URL(urlPath);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            if(sessionId !=null ){
                conn.setRequestProperty("Cookie",sessionId);
            }
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
//          conn.setReadTimeout(5000);

        ......省略号......

        } catch (Exception e) {
            e.printStackTrace();
}

        ......省略号......

    }持有webview的Activity  MainActivity

private CookieManager cookieManager;

cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
clearSession();

private void clearSession() {
    if (NetHelper.cookList != null) {
    cookieManager.removeSessionCookie();
    }
}

//在第一次请求的时候,设置一次session即可
private void setSession(String url) {
    if (NetHelper.cookList != null) {
        String values = NetHelper.cookList.toString();
        cookieManager.setCookie(url, values); //设置cookie
        CookieSyncManager.getInstance().sync(); //同步
    }
}

     跑马灯效果

导语

当系统控件不能满足我们的需求的时候,这时候我们就需要自定义控件,根据我们的需求来定制一个能满足我们需求的控件。一个让用户熟悉的控件才是一个好的控件,如果一味追求酷炫的效果,会让用户觉得华而不实。

2. 自定义控件的实现方案

自定义控件的实现方式(详细内容可以参考压缩包中的<自定义控件.pdf>):

1、继承方式

当简单控件不满足需求时,通过继承重写简单控件,实现对控件的定制。

2、组合方式

当单个控件不满足需求时,可以采用多个控件的组合,实现对控件的定制。

3、控件自绘方式

通过继承自view,重写onDraw方法实现。

项目中的具体应用:

1、登录邮箱的自动补全功能实现(纯代码实现布局)。
2、弹窗滚轮的实现(代码加布局文件)
3、TabButton的实现(两种实现方式)

A、 登录邮箱的自动补全功能实现:

效果:

澳门新葡萄京娱乐场 2

实现原理:

1、继承重写简单控件AutoCompleteTextView
2、编写自定义数据适配器和布局文件,并实现文字变化监听器
3、通过组合方式,实现右侧的删除图标。并根据焦点和文字的变化,动态显示右侧删除图标。

1、通过继承自简单控件AutoCompleteTextView实现帐号自动补全

关键代码:

public class AutoComplete extends AutoCompleteTextView {

    private static final String[] emailSuffix = {
        "@qq.com", "@163.com", "@126.com", "@gmail.com", "@sina.com", "@hotmail.com",
        "@yahoo.cn", "@sohu.com", "@foxmail.com", "@139.com", "@yeah.net", "@vip.qq.com",
        "@vip.sina.com"};

    ......省略号......

    //构造函数原型要正确,留给系统调用

    public AutoComplete(Context context) {
        super(context);
        mContext = context;
    }

    public AutoComplete(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    public void init(ImageView imageView) {
        mImageView = imageView;
        final MyAdatper adapter = new MyAdatper(mContext);
        setAdapter(adapter);
        addTextChangedListener(new TextWatcher() {
            @Override
            public void afterTextChanged(Editable s) {
                if (isTextWatch) {
                    String input = s.toString();

                    ......省略号......

                    adapter.clearList(); //注意要清空数据,根据输入的变化,自动生成数据
                    if (input.length() > 0) {
                        for (int i = 0; i < emailSuffix.length; ++i) {
                            adapter.addListData(input + emailSuffix[i]);
                        }
                    }
                    adapter.notifyDataSetChanged();
                    showDropDown();//该行代码会造成崩溃
                }
            }
        });
        //当输入一个字符的时候就开始检测
        setThreshold(1);
    }

    private class ViewHolder {
        TextView tv_Text;
    }

    class MyAdatper extends BaseAdapter implements Filterable {
        private List<String> mList;
        private Context mContext;
        private MyFilter mFilter;

        ......省略号......

        public void clearList() {
            mList.clear();
        }

        public void addListData(String strData) {
            mList.add(strData);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view;
            ViewHolder viewHolder;

            if (convertView == null) {
                view = LayoutInflater.from(mContext).inflate(R.layout.activity_autocomplete_item, null);
                viewHolder = new ViewHolder();
                viewHolder.tv_Text = (TextView) view.findViewById(R.id.tv_autocomplete);
                view.setTag(viewHolder);
            } else {
                view = convertView;
                viewHolder = (ViewHolder) view.getTag();
            }

            viewHolder.tv_Text.setText(mList.get(position));

            return view;
        }

        ......省略号......

    }

activity_autocomplete_item 下拉列表布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:background="@color/White"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv_autocomplete"
        android:padding="15dp"
        android:textSize="20sp"
        android:singleLine="true"
        android:textColor="@color/Black"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

上面自动补全的效果图:

澳门新葡萄京娱乐场 3

2、通过组合方式实现帐号自动补全复杂控件

关键代码:

public class AdvancedAutoCompleteTextView extends RelativeLayout {
    private Context mContext;
    private AutoComplete mAutoComplete; //上面的自定义控件
    private ImageView mImageView;       //右侧的图标控件

    ......省略号......

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        initViews();
    }
    //代码方式,初始化布局
    private void initViews() {
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
        params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
        params.addRule(RelativeLayout.CENTER_VERTICAL);
        mAutoComplete = new AutoComplete(mContext);
        mAutoComplete.setLayoutParams(params);
        mAutoComplete.setPadding(0, 0, 40, 0);
        mAutoComplete.setSingleLine(true);
        mAutoComplete.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
        mAutoComplete.setFitsSystemWindows(true);
        mAutoComplete.setEms(10);
        mAutoComplete.setHint("URS账号");
        mAutoComplete.setImeOptions(EditorInfo.IME_ACTION_NEXT
                                | EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN);
        mAutoComplete.setDropDownHorizontalOffset(0);
        mAutoComplete.setDropDownVerticalOffset(2);
        mAutoComplete.setBackgroundResource(R.drawable.edit_text_background);

        RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, LayoutParams.FILL_PARENT);
        p.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
        p.addRule(RelativeLayout.CENTER_VERTICAL);
        p.rightMargin = 10;
        mImageView = new ImageView(mContext);
        mImageView.setLayoutParams(p);
        mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
        mImageView.setImageResource(R.drawable.unselect);
        mImageView.setClickable(true);
        mImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setText("");
            }
        });

        this.addView(mAutoComplete);
        this.addView(mImageView);
        //监听获取焦点事件,目的:输入帐号时,右侧图标的显示
        mAutoComplete.setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus && !mAutoComplete.getText().toString().isEmpty()) {
                    mAutoComplete.setShow(false); //如果获取首次获取焦点,此时文本不为空,则显示,并禁止文本改变监听里的设置
                    mImageView.setImageResource(R.drawable.item_delete);
                } else if (hasFocus) {
                    mAutoComplete.setShow(true);//如果获取首次获取焦点,此时文本为空,则不改变,并开启文本改变监听里的设置
                } else {
                    mAutoComplete.setShow(false);
                    mImageView.setImageResource(R.drawable.unselect);
                }
            }
        });

                 //对AutoComplete自定义控件初始化,一定要放到最后.否则,会由于AutoComplete初始化未完成,就弹窗,而崩溃

        mAutoComplete.init(mImageView); 
    }
}

B、弹窗滚轮的实现

效果:

澳门新葡萄京娱乐场 4

实现原理:

1、继承重写简单控件ScrollView,实现滚动效果,并添加回调接口,用于获取选择的内容。
2、为自定义控件添加内容,其中每一项为一个TextView,用于内容显示。
3、通过自绘添加上下两条直线,实现选中状态。
4、最后利用popup弹窗,加载整个视图,显示弹窗滚动效果。

1、通过继承ScrollView实现滚动,并向布局添加具体项

关键代码:

public class WheelView extends ScrollView {

    //选择后的回调接口
    public interface OnWheelViewListener {
        void onSelected(int selectedIndex, String item);
    }

    ......省略号......

    //初始化,并创建布局
    private void init(Context context) {
        this.context = context;
        this.setVerticalScrollBarEnabled(false);

        views = new LinearLayout(context);  //为自定义控件创建线性布局
        views.setOrientation(LinearLayout.VERTICAL);
        this.addView(views);

        //异步任务,根据滚动的位置自动调整待显示的数据,该异步任务会在滚动事件触发式执行
        scrollerTask = new Runnable() {
            public void run() {
                if (itemHeight == 0) {
                    return;
                }
                int newY = getScrollY();
                if (initialY - newY == 0) { // stopped
                    final int remainder = initialY % itemHeight;
                    final int divided = initialY / itemHeight;

                    if (remainder == 0) {
                        selectedIndex = divided + offset;
                        onSeletedCallBack();
                    } else {
                        if (remainder > itemHeight / 2) {
                            WheelView.this.post(new Runnable() {
                                @Override
                                public void run() {
                                    WheelView.this.smoothScrollTo(0, initialY - remainder + itemHeight);
                                    selectedIndex = divided + offset + 1;
                                    onSeletedCallBack();
                                }
                            });
                        } else {
                            WheelView.this.post(new Runnable() {
                                @Override
                                public void run() {
                                    WheelView.this.smoothScrollTo(0, initialY - remainder);
                                    selectedIndex = divided + offset;
                                    onSeletedCallBack();
                                }
                            });
                        }
                    }
                } else {
                    initialY = getScrollY();
                    WheelView.this.postDelayed(scrollerTask, newCheck);
                }
            }
        };
    }

    //往布局添加数据

    private void initData() {
        displayItemCount = offset * 2 + 1;

        //添加新view之前,必须移除旧的,否则不正确
        views.removeAllViews();

        for (String item : items) {
            views.addView(createView(item));
        }

        refreshItemView(0);
    }

    private TextView createView(String item) {
        TextView tv = new TextView(context);
        tv.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        tv.setSingleLine(true);
        tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
        tv.setText(item);
        tv.setGravity(Gravity.CENTER);
        int padding = dip2px(15);
        tv.setPadding(padding, padding, padding, padding);
        if (0 == itemHeight) {
            itemHeight = getViewMeasuredHeight(tv);
            views.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, itemHeight * displayItemCount));
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) this.getLayoutParams();
            this.setLayoutParams(new LinearLayout.LayoutParams(lp.width, itemHeight * displayItemCount));
        }
        return tv;
    }

    ......省略号......

    @Override   //上下直线的自绘
    public void setBackgroundDrawable(Drawable background) {

        if (viewWidth == 0) {
            viewWidth = ((Activity) context).getWindowManager().getDefaultDisplay().getWidth();
        }

        if (null == paint) {
            paint = new Paint();
            paint.setColor(Color.parseColor("#83cde6"));
            paint.setStrokeWidth(dip2px(1f));
        }

        background = new Drawable() {
            @Override
            public void draw(Canvas canvas) {
                canvas.drawLine(viewWidth * 1 / 6, obtainSelectedAreaBorder()[0], viewWidth * 5 / 6, 

obtainSelectedAreaBorder()[0], paint);

                canvas.drawLine(viewWidth * 1 / 6, obtainSelectedAreaBorder()[1], viewWidth * 5 / 6, 

obtainSelectedAreaBorder()[1], paint);

            }
        };

        super.setBackgroundDrawable(background);
    }

}

2、动态加载布局,并利用PopupWindow弹窗显示。

关键代码:

private void addView(int num){

    ......省略号......

    wheel_layout_view = LayoutInflater.from(this).inflate(R.layout.wheel_view, null);

    ......省略号......

 }

布局文件 wheel_view 效果图

澳门新葡萄京娱乐场 5

private void popupWindows(List<String> list){
    if (wheel_layout_view != null){

        mPopupWindow = null;
        mPopupWindow = new PopupWindow(wheel_layout_view);
        mPopupWindow.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
        mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);

        //点击外部,自动消失
        mPopupWindow.setFocusable(true);
        mPopupWindow.setOutsideTouchable(true);

        ......省略号......

        mPopupWindow.showAtLocation(ll_weidu_condition, Gravity.BOTTOM, 0, 0);
    }
}

C、TabButton的实现

效果:

澳门新葡萄京娱乐场 6
1、利用.9.png图标实现(简单、美观)

属性定义attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 自定义的button控件,用于日期的选择-->
    <declare-styleable name="TabButton">
        <attr name="normal_bg_res" format="reference" />
        <attr name="selected_bg_res" format="reference" />
    </declare-styleable>
</resources>

布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"   //声明自定义属性空间

    ......省略号......

    android:orientation="vertical">

    ......省略号......

        <xxxxxxxxxxx.customui.TabButton
            style="@style/commonButton"
            android:layout_width="0dp"
            android:layout_margin="0dp"
            android:layout_weight="1"
            android:layout_height="40dp"
            android:text="昨天"
            android:textSize="22sp"
            android:gravity="center"
            android:background="@drawable/btn_left"
            android:textColor="@color/blue"
            custom:normal_bg_res="@drawable/btn_left"
            custom:selected_bg_res="@drawable/btn_left_selected"
            android:id="@+id/bt_yesterday" />

    ......省略号......

</LinearLayout>

关键代码:

public class TabButton extends Button {
    private int normal_bg_res;
    private int selected_bg_res;

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

    public TabButton(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.TabButton);
        normal_bg_res = typeArray.getResourceId(R.styleable.TabButton_normal_bg_res, 0);
        selected_bg_res = typeArray.getResourceId(R.styleable.TabButton_selected_bg_res, 0);

        typeArray.recycle();
    }

    public void setSelected(boolean selected) {
        if (selected) {
            setBackgroundResource(selected_bg_res);
            setTextColor(Color.WHITE);
        } else {
            setBackgroundResource(normal_bg_res);
            setTextColor(getResources().getColor(R.color.blue));
        }
    }
}

2、利用布局文件实现(复杂、灵活)。

更多样式,可以参数官方的SDK(android-sdk-windowsplatformsandroid-1.5datares)

布局样式button_style:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <solid android:color="#0d76e1" />
        </shape>
    </item>

    <item android:state_focused="true">
        <shape android:shape="rectangle">
            <solid android:color="@color/Grey" />
        </shape>
    </item>

    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/Grey" />
        </shape>
    </item>
</selector>

样式应用:

<Button android:id="@+id/tab_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/button_style">

  1.用过属性的方式实现跑马灯效果

         属性:

                 android:singleLine=”true”
这个属性是设置TextView文本中文字以省略号的形式收缩文本内容

                 android:focusable=”true”:启动跑马效果

         澳门新葡萄京娱乐场 7   

         澳门新葡萄京娱乐场 8

<TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:text="这是跑马灯实现,我是跑马灯,我要跑起来了,跑起来了跑呀跑呀" />

         
 那我们定义多个TextView实现跑马灯看看有什么效果?我们可以在activity_main.xml中加多一个TextView控件,可以看到如下显示

     澳门新葡萄京娱乐场 9  

     
很显然当我们定义多个TextView时候第二个TextView着跑不起来了,所以这很显然不能满足我们的需求这时我们可以自定义的方式来实现,这样就灵活的操作TextView了这样

就可以看上去方便许多

主要内容

  • 了解自定义控件
  • 对现有控件进行拓展
  • 创建复合控件
  • 重写View来实现全新控件
  • 自定义ViewGroup

3. 蒙板效果的实现

1、不保留标题栏蒙板的实现

效果:

澳门新葡萄京娱乐场 10

原理:

1、弹窗时,设置背景窗体的透明度
2、取消弹窗时,恢复背景窗体的透明度

关键代码:

private void popupWindows(List<String> list){
    //产生背景变暗效果
    WindowManager.LayoutParams lp=getWindow().getAttributes();
    lp.alpha = 0.4f;
    getWindow().setAttributes(lp);

        ......省略号......

    mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                WindowManager.LayoutParams lp = getWindow().getAttributes();
                lp.alpha = 1f;
                getWindow().setAttributes(lp);
            }
        });

      ......省略号......

}

2、保留标题栏蒙板的实现

效果:

澳门新葡萄京娱乐场 11

原理:

1、根据需求,设置蒙板布局大小。
2、弹窗时,显示蒙板布局
2、取消弹窗时,隐藏蒙板布局

关键代码:

1、蒙板布局实现:

<!-- popup蒙板 -->
<LinearLayout
    android:id="@+id/ll_popup_hide"
    android:layout_width="match_parent"
    android:background="@color/hide_bg"
    android:orientation="vertical"
    android:layout_height="match_parent">
</LinearLayout>

<color name="hide_bg">#88323232</color>

2、代码处理

ll_popup_hide.setVisibility(View.VISIBLE); //显示蒙板
ll_popup_hide.setVisibility(View.INVISIBLE); //隐藏蒙板

    2.自定义跑马灯

          实现步骤:

             1.定义一个普通类用于继承TextView控件对象

             2.重写相应的构造方法和isFocused()方法 

public class MyTextView extends TextView {

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

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

    @Override
    @ExportedProperty(category = "focus")
    public boolean isFocused() {
        //将其返回值设置为true
        return true ;
    }
}

       3.引用我们自定义的控件

 <com.example.runmudeng.MyTextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:text="这是跑马灯实现,我是跑马灯,我要跑起来了,跑起来了跑呀跑呀" />

    <com.example.runmudeng.MyTextView
        android:layout_below="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:text="这是跑马灯实现,我是跑马灯,我要跑起来了,跑起来了跑呀跑呀" />

    4.检查结果

      澳门新葡萄京娱乐场 12

具体内容

自定义控件可以是对现有控件进行拓展、创建复合控件、重写View实现全新控件,可根据需要不同选择最佳方式进行自定义控件。

4. Activity的回收与操作超时的处理

1、Activity的回收

针对多个activity退出的处理

关键代码:

1、新建活动管理类:

public class ActivityCollector {
    private static List<Activity> activityList = new ArrayList<Activity>();
    public static void addActivity(Activity activity){
        activityList.add(activity);
    }
    public static void removeActivity(Activity activity){
        activityList.remove(activity);
    }

    public static void finishAllButLast(){
        Activity activity = activityList.get(activityList.size()-1);
        removeActivity(activity);

        for (Activity activityItem: activityList){
            if (!activityItem.isFinishing()){
                activityItem.finish();
            }
        }

        activityList.clear();
        activityList.add(activity);
    }

    public static void finishAll(){
        for (Activity activity: activityList){
            if (!activity.isFinishing()){
                activity.finish();
            }
        }

        activityList.clear();
    }
}

2、创建基类BaseActivity,并使所有的activity继承自该基类
。在创建时,添加到活动管理器,销毁时,从活动管理器中移除。

public class BaseActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }
}

如果需要销毁所有activity,只需调用finishAll()即可

2、操作超时处理

原理:

1、在activity的stop函数中,根据app进程IMPORTANCE_FOREGROUND判断app在前台或后台
2、在activity的onResume函数中,做超时检查。

关键代码:

abstract public class TimeOutCheckActivity extends BaseActivity {
    private boolean isLeave = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        pref = getSharedPreferences(Constant.CONFIG_NAME, Context.MODE_PRIVATE);
    }

    /**
     * 回调函数,方便测试
     * @return
    */
    abstract protected String getTag();

    ......省略号......

    /***
     * 当用户使程序恢复为前台显示时执行onResume()方法,在其中判断是否超时.
     */
    @Override
    protected void onResume() {
//        Log.i("Back",getTag() + ",onResume,是否在前台:" + isOnForeground());
        super.onResume();
        if (isLeave) {
            isLeave = false;
            timeOutCheck();
        }
    }

    @Override 
    protected void onStop() {
        super.onStop();
        if (!isOnForeground()){
            if (!isLeave && isOpenALP()) {
                isLeave = true;
                saveStartTime();
            }
        }
    }

    public void timeOutCheck() {
        long endtime = System.currentTimeMillis();
        if (endtime - getStartTime() >= Constant.TIMEOUT_ALP * 1000) {
            Util.toast(this, "超时了,请重新验证");
            String alp = pref.getString(Constant.ALP, null);
            if (alp == null || alp == "") {
            } else {
                Intent intent = new Intent(this, UnlockGesturePasswordActivity.class);
                intent.putExtra("pattern", alp);
                intent.putExtra("login",false); //手势验证,不进行登录验证
                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
                // 打开新的Activity
                startActivityForResult(intent, Constant.REQ_COMPARE_PATTERN_TIMEOUT_CHECK);
            }
        }
    }

    public void saveStartTime() {
        pref.edit().putLong(Constant.START_TIME, System.currentTimeMillis()).commit();
    }

    public long getStartTime() {
        long startTime = 0;
        try {
            startTime = pref.getLong(Constant.START_TIME, 0);
        }catch (Exception e){
            startTime = 0;
        }
        return startTime;
    }

    /**
     * 程序是否在前端运行,通过枚举运行的app实现。防止重复超时检测多次,保证只有一个activity进入超时检测
     *当用户按home键时,程序进入后端运行,此时会返回false,其他情况引起activity的stop函数的调用,会返回true
     * @return
    */
    public boolean isOnForeground() {
        ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
        String packageName = getApplicationContext().getPackageName();

        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
        if (appProcesses == null)
            return false;

        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.processName.equals(packageName)
                    && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { 
                return true;
            }
        }

        return false;
    }
}

补充说明:

可以根据importance的不同来判断前台或后台,RunningAppProcessInfo
里面的常量IMTANCE就是上面所说的前台后台,其实IMOPORTANCE是表示这个app进程的重要性,因为系统回收时候,会根据IMOPORTANCE来回收进程的。具体可以去看文档。

public static final int IMPORTANCE_BACKGROUND = 400//后台 
public static final int IMPORTANCE_EMPTY = 500//空进程 
public static final int IMPORTANCE_FOREGROUND = 100//在屏幕最前端、可获取到焦点 可理解为Activity生命周期的OnResume(); 
public static final int IMPORTANCE_SERVICE = 300//在服务中 
public static final int IMPORTANCE_VISIBLE = 200//在屏幕前端、获取不到焦点可理解为Activity生命周期的OnStart();

     搜索提示快速查找

了解自定义控件

在自定义View时,我们通常会去重写onDraw()方法来绘制View的显示内容。如果该View还需要使用wrap_content属性,那么还必须重写onMeasure()方法。另外,通过自定义attrs属性,还可以设置新的属性配置值。
在View中通常有以下一些比较重要的回调方法:

  • onFinishInflate():从XML加载组件后回调。
  • onSizeChanged():组件大小改变时回调。
  • onMeasure():回调该方法来进行测量。
  • onLayout():回调该方法来确定显示的位置。
  • onTouchEvent():监听到触摸事件时回调。

当然,创建自定义View的时候,并不需要重写所有的方法,只需要重写特定条件的回调方法即可。这也是Android控件架构灵活性的体现。
在通常情况下,有以下三种方法来实现自定义的控件:

  • 对现有控件进行拓展
  • 创建复合控件
  • 重写View来实现全新控件

           AutoCompleteTextView控件

         
 在很多apk中都运用了这个功能操作,就像百度上当你输入一个android单词着会显示下拉提示供你选择点击,那如何实现下面图片的显示的那个功能呢?

我们着需要运用一个AutoCompleteTextView控件进行实现这个功能操作

       1.添加控件

         功能:可以动态的匹配输入的内容,可以根据显示匹配热门信息

        属性: android:completionThreshold=”3″
:这个属性是设置当你输入到3个字符的时候将列出提示内容

 <AutoCompleteTextView
        android:completionThreshold="3"
        android:id="@+id/autoCompleteTextView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入你要查询的内容" 
        >
 </AutoCompleteTextView>

  2.实现操作

               1)首先我们初始化控件             2)定义一个适配器对象

               3)初始化数据源                    
 4)将适配器赋值到控件中

public class MainActivity extends Activity {
    //初始化一个AutoCompleteTextView对象
    private AutoCompleteTextView act;
    //初始化数据源
    private String[] data = {"android","onClick","android2","Struts","Spring",};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /**
         * 1实例化控件对象
         * 2需要定义一个适配器对象
         * 3初始化数据源
         * 4.将适配器赋值到控件对象中
         */
        act = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_expandable_list_item_1,data);

        act.setAdapter(adapter);
    }

}

对现有控件进行拓展

这是一个非常重要的自定义View方法,它可以在原生控件的基础上进行拓展,增加新的功能、修改显示的UI等。一般来说我们可以在onDraw()方法中对原生控件行为进行拓展。
以一个TextView为例,使用Canvas对象来进行图像的绘制,然后利用Android的绘图机制,可以绘制出更加复杂丰富的图像。比如可以利用LinearGradient
Shader和Matrix来实现一个动态的文字闪动效果,程序运行效果如下图所示。

澳门新葡萄京娱乐场 13

闪动的文字

要想实现这一个效果,可以充分利用Android中Paint对象的Shader渲染器。通过设置一个不断变化的LinearGradient,并使用带有该属性的Paint对象来绘制要显示的文字。首先,在onSizeChanged()方法中进行一些对象的初始化工作,并根据View的宽度设置一个LinearGradient渐变渲染器,代码如下:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh){
    super.onSizeChanged(w, h, oldw, oldh);
    if(mViewWidth  == 0) {
        mViewWidth = getMeasuredWidth();
        if(mViewWidth > 0) {
            mPaint = getPaint();
            // 创建mLinearGradient渐变渲染器并填充到画笔mPaint
            mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0, 
                    new int[] {Color.BLUE, 0xffffffff, Color.BLUE}, 
                    null, Shader.TileMode.CLAMP);
            mPaint.setShader(mLinearGradient);
            mGradientMatrix = new Matrix();
        }
    }
}

其中最关键的就是使用getPaint()方法获取当前绘制TextView的Paint对象,并给这个Paint对象设置原生TextView没有的LinearGradient属性。最后,在onDraw()方法中,通过矩阵的方式来不断平移渐变效果,从而在绘制文字时,产生动态的冷却效果,代码如下所示:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if(mGradientMatrix != null) {
        mTranslate += mViewWidth / 5;
        if(mTranslate > 2 * mViewWidth) {
            mTranslate = -mViewWidth;
        }
        mGradientMatrix.setTranslate(mTranslate, 0);  // 设置位移
        mLinearGradient.setLocalMatrix(mGradientMatrix);
        postInvalidateDelayed(100);  // 延时刷新
    }
}

这样就完成了对TextView进行拓展,制作一个动态文字闪动的TextView。

     MultiAutoCompleteTextView控件

             
功能:和上面的AutoCompleteTextView功能是一样的,只是该控件的功能更强大,它支持多选择操作 

              属性: android:completionThreshold=”3″
:这个属性是设置当你输入到3个字符的时候将列出提示内容

       
1该控件支持多条件选择所以它要设置我们要选择时定义一个分割符,而这分割符是内部提供的方法进行操作

这次同样的在该Android工程下的activity_main.xml中添加另外的一个控件MultiAutoCompleteTextView通过这个控件可以实现多搜索选择操作

<MultiAutoCompleteTextView
        android:completionThreshold="3"
        android:id="@+id/multiAutoCompleteTextView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/autoCompleteTextView1"
        android:hint="请选择多个你要查询的内容" >
</MultiAutoCompleteTextView>

 

     
 2.实现同样操作,在MainActivity.java中获取该控件的对象,然后操作步骤如上

                1)首先我们初始化控件             2)定义一个适配器对象

                3)初始化数据源                    
 4)将适配器赋值到控件中     5)设置分割符

public class MainActivity extends Activity {

    //初始化一个AutoCompleteTextView对象
    private AutoCompleteTextView act;
    //初始化数据源
    private String[] data = {"android","onClick","android2","Struts","Spring",};
    //初始化一个MultiAutoCompleteTextView对象
    private MultiAutoCompleteTextView mact ;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /**
         * 1实例化控件对象
         * 2需要定义一个适配器对象
         * 3初始化数据源
         * 4.将适配器赋值到控件对象中
         */
        act = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_expandable_list_item_1,data);
        act.setAdapter(adapter);

        /**
         * 1实例化控件对象
         * 2需要定义一个适配器对象
         * 3初始化数据源
         * 4.将适配器赋值到控件对象中
         * 5设置分隔符
         */
        mact = (MultiAutoCompleteTextView) findViewById(R.id.multiAutoCompleteTextView1);
        ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(this,android.R.layout.simple_expandable_list_item_1,data);
        mact.setAdapter(adapter2);
        mact.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());

    }
}

      澳门新葡萄京娱乐场 14 
 澳门新葡萄京娱乐场 15

创建复合控件

创建复合控件可以很好地创建出具体重用功能的控件集合。这种方式通常需要继承一个合适的ViewGroup,再给它添加指定功能的控件,从而组合成新的复合控件。通过这种方式创建的控件,我们一般会给它指定一些可配置的属性,让它具有更强的拓展性。
下面就以一个通用的TopBar为示例,进行讲解如何创建复合控件。效果如下所示。

澳门新葡萄京娱乐场 16

复合控件TopBar

首先新建一个TopBar类继承RelativeLayout。

public class TopBar extends RelativeLayout {

    public TopBar(Context context) {
        this.TopBar(context, null);
    }

    public TopBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 初始化的方法
        // 初始化属性
        initAttr(context, attrs)
        // 初始化布局
        initView(context);
        // 初如化事件
        initEvent();
    }

}

         ToggleButton控件

     
1.什么是ToggleButton:可以通过toggle这个单词就指定,就像开关操作,当你点击一下则开,点击一下着关

            状态:分为选中状态和未选中状态
,并且为不同的状态设置不同的显示文本内容

            属性:android:checked=” ”  textOff=””  textOn=””   

                      check属性:默认是false

                      textOff,textOn:是设置你开关值

        1.首先我们在布局文件中添加一个控件ToggleButton

<ToggleButton
        android:id="@+id/toggleButton1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textOn="开"
        android:textOff="关"
        />

          2.编写代码获取你点击的状态

public class MainActivity extends Activity {

    //声明一个ToggleButton对象
    private ToggleButton togglebt ;
    private ImageView imgView ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        togglebt = (ToggleButton) findViewById(R.id.toggleButton1);
        imgView = (ImageView) findViewById(R.id.img);
        //给toggleButton绑定一个事件
        togglebt.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                //获取当前点击时是否为true
                if(isChecked){

                    imgView.setBackgroundResource(R.drawable.on);
                }else{
                    imgView.setBackgroundResource(R.drawable.off);
                }
            }

        });
    }

}

 

定义属性

为一个View提供可自定义的属性非常简单,只需要在res资源目录的values目录下创建一个attrs.xml的属性定义文件,并在该文件中通过如下代码定义相应的属性即可。

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <declare-styleable name="TopBar">
        <!-- 定义title文字,大小,颜色 -->
        <attr name="title" format="string" />
        <attr name="titleTextSize" format="dimension" />
        <attr name="titleTextColor" format="color" />
        <!-- 定义left 文字,大小,颜色,背景 -->
        <attr name="leftTextColor" format="color" />
        <attr name="leftTextSize" format="dimension" />
        <!-- 表示背景可以是颜色,也可以是引用 -->
        <attr name="leftBackground" format="reference|color" />
        <attr name="leftText" format="string" />
        <!-- 定义right 文字,大小,颜色,背景 -->
        <attr name="rightTextColor" format="color" />
        <attr name="rightTextSize" format="dimension"/>
        <attr name="rightBackground" format="reference|color" />
        <attr name="rightText" format="string" />
    </declare-styleable>

</resources>

我们在代码中通过<declare-styleable>标签声明了使用自定义属性,并通过name属性来确定引用的名称,通过<attr>标签来声明具体的自定义属性,比如在这里定义了标题文字的内容、大小、颜色,左右按钮的背景、文字内容、颜色等属性,并通过format属性来指定属性的类型。这里需要注意的是,有些属性可以是颜色属性,也可以是引用属性。比如按键的背景,所以使用“|”来分隔不同的属性——”reference|color”。
在确定好属性后,就可以创建一个自定义控件——TopBar,并让它继承自ViewGroup,从而组合一些需要的控件。这里为了简单,我们继承RelativeLayout。在构造方法中,通过如下所示代码来获取在XML布局文件中自定义的那些属性,即与我们使用系统提供的那些属性一样。

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);

系统提供了TypedArray这样的数据结构来获取自定义属性集,后面引用的styleable的TopBar,就是我们在XML中通过<declare-styleable
name=”TopBar”>所指定的name名。接下来,通过TypeArray对象的getString()、getColor()等方法,就可以获取这些定义的属性值,代码如下所示。

private int mLeftTextColor;
private Drawable mLeftBackground;
private String mLeftText;
private float mLeftTextSize;

private int mRightTextColor;
private Drawable mRightBackground;
private String mRightTextSize;
private float mRightTextSize;

private String mTitleText;
private float mTitleTextSize;
private int mTitleTextColor;

private void initAttr(Context context, AttributeSet attrs) {
    // 通过这个方法,将你在attrs.xml中定义的declare-styleable的所有属性的值存储到TypedArray.
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);
    // 从TypedArray中取出对应的值来为要设置的属性赋值
    mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor, 0);
    mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftBackground);
    mLeftText = ta.getString(R.styleable.TopBar_leftText);
    mLeftTextSize = typed.getDimension(R.styleable.TitleBar_leftTextSize, 20);

    mRightTextColor = ta.getColor(R.styleable.TopBar_rightTextColor, 0);
    mRightBackground = ta.getDrawable(R.styleable.TopBar_rightBackground);
    mRightText = ta.getString(R.styleable.TopBar_rightText);
    mRightTextSize = typed.getDimension(R.styleable.TitleBar_rightTextSize, 20);

    mTitleText = ta.getString(R.styleable.TopBar_titleText);
    mTitleTextSize = ta.getDimension(R.styleable.TopBar_titleTextSize, 10);
    mTitleTextColor = ta.getColor(R.styleable.TopBar_titleTextColor, 0);

// 获取完TypedArray的值后,一般要调用recyle()方法来避免重新创建的时候的错误
ta.recycle();
}

这里需要注意的是,当获取完所有的属性值后,需要调用TypedArray的recyle()方法来完成资源的回收。

       CheckBox复选框

<CheckBox
        android:checked="true"
        android:id="@+id/checkBox1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox" />

 

组合控件

接下来,我们就可以开始组合控件了。TopBar由三个控件组成,左边按钮mLeftButton、右边按钮mRightButton、中间标题栏mTitleView。通过动态添加控件的方式,使用addView()方法将三个控件加入到定义的TopBar模板中,并给它们设置我们前面所获取到的具体的属性值,比如标题的文字、颜色、大小等,代码如下所示。

private TextView mTitleView;
private Button mLeftButton;
private Button mRightButton;

private RelativeLayout.LayoutParams mLeftParams;
private RelativeLayout.LayoutParams mRightParams;
private RelativeLayout.LayoutParams mTitleParams;

private void initView(Context context) {
    mTitleView = new TextView(context);
    mLeftButton = new Button(context);
    mRightButton = new Button(context);

    // 为创建的组件赋值,值就来源于引用的xml文件中给对应属性的赋值
    mTitleView.setText(mTitleText);
    mTitleView.setTextSize(mTitleTextSize);
    mTitleView.setTextColor(mTitleTextColor);
    mTitleView.setGravity(Gravity.CENTER);

    mLeftButton.setText(mLeftText);
    mLeftButton.setTextColor(mLeftTextColor);
    mLeftButton.setBackgroundDrawable(mLeftBackground);
    mLeftButton.setTextSize(mLeftTextSize);

    mRightButton.setText(mRightText);
    mRightButton.setTextSize(mRightTextSize);
    mRightButton.setBackgroundDrawable(mRightBackground);
    mRightButton.setTextColor(mRightTextColor);

    // 为组件元素设置相应的布局元素
    // 设置布局的layout_width和layout_height属性
    mLeftParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
    // 该方法表示所设置节点的属性必须关联其他兄弟节点或者属性值为布尔值。
    mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
    // 动态添加组件
    addView(mLeftButton, mLeftParams);

    mRightParams= new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
    mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
    addView(mRightButton, mRightParams);

    mTitleParams= new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
    mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
    addView(mTitleView, mTitleParams);
}

既然是UI模板,那么每个调用者所需要这些按钮的实现功能都是不一样的。因此,不能直接在UI模板里实现逻辑,可以通过接口回调的思想,实现逻辑交给调用者。实现过程如下所示。

  • 定义接口
    定义一个左右按钮点击的接口,并创建两个方法,分别用于左右两个按钮的点击,代码如下所示。

// 在类内部定义一个接口对象,实现回调机制,不用去考虑如何实现,具体实现由调用者去创建
public interface OnClickListener{
    // 左按钮点击事件
    void leftClick();
    // 右按钮点击事件
    void rightClick();
    }
  • 暴露接口给调用者
    在模板方法中,为左右按键增加点击事件,但不实现具体逻辑,而是调用接口中相应的点击方法,代码如下所示。

// 创建一个接口对象
private OnClickListener mListener;

// 暴露一个方法给调用者来注册接口,通过接口来获得回调者对接口方法的实现
public void setOnClickListener(OnClickListener listener) {
    this.mListener = listener;
}

private void initEvent(){
    // 按钮的点击事件,不需要具体的实现,只需要调用接口方法,回调的时候会有具体实现
    mLeftButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            mListener.leftClick();
        }
    });

    mRightButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            mListener.rightClick();
        }
    });
}
  • 实现接口回调
    在调用者的代码中,调用者需要实现这样一个接口,并完成接口中的方法,确定具体的实现逻辑,并使用第二步中暴露的方法,将接口的对象传递进去,从而完成回调。通常情况下,可以使用匿名内部类的形式来实现接口中的方法,代码如下所示。

mTopBar.setOnClickListener(new TopBar.OnClickListener() {
    @Override
    public void leftClick() {
        // 点击左边按钮
    }

    @Override
    public void rightClick() {
        // 点击右边按钮
    }
}

在方法中分别添加点击左右按钮之后的逻辑代码。

除了通过接口回调的方式来实现动态的控制UI模板,同样可以使用公共方法来动态地修改UI模板中的UI,这样就进一步提高了模板的可定制性,代码如下所示。

public static final int LEFT = 1;
public static final int RIGHT = 2;

/**
  * 设置按钮的显示与否通过常量区分,visible区分是否显示
  *
  * @param view  标记View
  * @param visible  是否显示
  * /
public void setVisable(int view, int visible){
    switch(view) {
        case LEFT:
            mLeftButton.setVisibility(visible);
        break;
        case RIGHT:
            mRightButton.setVisibility(visible);
        break;
    }
}

通过如上代码,调用者通过TopBar对象调用这个方法后,根据参数,可以动态地控制按钮的显示,代码如下。

// 控制TopBar上组件的状态
mTopBar.setVisable(TopBar.LEFT, View.VISIBLE);
mTopBar.setVisable(TopBar.RIGHT, View.GONE);

      RadioGroup和RadioButton 实现底部选择栏

<RadioGroup
        android:id="@+id/radioGroup1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <RadioButton
            android:id="@+id/radio0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            android:text="RadioButton" />

        <RadioButton
            android:id="@+id/radio1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="RadioButton" />
    </RadioGroup>

 

       
 说起RadioGroup在实际的开发中运用非常广,在很多的apk中都基本上运用了该组件实现了底部选栏的效果

接下来小编带大家实现微信底部选栏 澳门新葡萄京娱乐场 17

       1.我们定义一个布局文件为activity_footer.xml

<?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="horizontal" >

    <RadioGroup
        android:id="@+id/radioGroup1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:background="@drawable/group_buton_nomal"
         >

         <RadioButton
               android:drawableTop="@drawable/_mainframe"
            android:id="@+id/radio0"
            android:checked="true"
            android:text="@string/_weixi_text"
            style="@style/radio_group" 
            android:textColor="@drawable/_mainframe"
           />

        <RadioButton
            android:drawableTop="@drawable/content"
            android:id="@+id/radio1"
            android:text="@string/_str_address" 
            style="@style/radio_group" 
            android:textColor="@drawable/content"/>

        <RadioButton
            android:drawableTop="@drawable/_discover"
            android:id="@+id/radio2"
            android:text="@string/_str_find" 
            style="@style/radio_group"
            android:textColor="@drawable/_discover" />

       <RadioButton
               android:drawableTop="@drawable/_me"
            android:id="@+id/radio3"
            android:text="@string/_str_me"
            style="@style/radio_group" 
            android:textColor="@drawable/_me"
            />

    </RadioGroup>

</LinearLayout>

 

         
我们可以通过RadioGroup组件中的一个属性android:orientation=”horizontal”设置该组件中的内容为水平排列 

  然后我将一些公共的样式抽取到一个style.xml中

  <style name="radio_group">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
           <item name="android:layout_weight">1</item>
           <item name="android:button">@null</item>
           <item name="android:gravity">center</item>
           <item name="android:textSize">10dp</item>
    </style>

 

       
如何将一个按钮变化为一个图片呢?很简单可以通过 android:button=”@null”/>

 这时你可以看到如下的布局了澳门新葡萄京娱乐场 18是不是很快呢??

       2.
这时我们要设置当你点击的时候可以切换图片和字体颜色,我们需要定义一个xml来帮我实现该效果

           1)我们在drawable目录下分别为这四个案例设置xml文件

         澳门新葡萄京娱乐场 19

             xxx.xml内容

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

    <!-- 点击的时候改变图片,并且字体设置颜色  -->
    <item android:state_checked="true" android:drawable="@drawable/tabbar_discoverhl" android:color="@color/green"/>
    <item android:drawable="@drawable/tabbar_discover" android:color="@color/grey"/>

</selector>

 

           
当你点击时为true将设置有颜色的图片和将字体改为绿色,当你编写完这4个文件之后,这时候你要分别在每个radioButton中引用这四个文件

如:
<RadioButton
      android:drawableTop="@drawable/_me"//图片引用
      android:id="@+id/radio3"
      android:text="@string/_str_me"
      style="@style/radio_group"
      android:textColor="@drawable/_me"//字体引用
      />

       澳门新葡萄京娱乐场 20恭喜你操作完成

 

     

 

        

 

           

引用UI模板

最后一步,自然是在需要使用的地方引用UI模板,在引用前,需要指定引用第三方控件的名字空间。在布局文件中,可以看到如下一行代码。

xmlns:android="http://schemas.android.com/apk/res/android"

这行代码就是在指定引用的名字空间xmlns,即xml
namespace。这里指定了名字空间为“android”,因此在接下来使用系统属性时,才可以使用“android:”来引用Android的系统属性。同样,如果要使用自定义属性,那么就需要创建自己的名字空间,在Android
Stuido中,第三方控件都使用如下代码来引用名字空间。

xmlns:app="http://schemas.android.com/apk/res/res-auto"

这里我们将引入的第三方控件的名字空间取名为app,之后在XML文件中使用自定义的属性时,就可以通过这个名字空间来引用,代码如下所示。

<com.example.demo.TopBar
        android:id="@+id/tb"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_alignParentBottom="true"
        app:leftBackGround="#ff000000"
        app:leftText="Back"
        app:leftTextColor="#ffff6734"
        app:leftTextSize="25dp" 
        app:rightText="More"
        app:rightTextSize="25dp"
        app:rightTextColor="#ff123456"
        app:title="自定义标题"
        app:titleTextColor="#ff654321"/>

使用自定义的View与系统原生的View最大的区别就是在申明控件时,需要指定完整的包名,而在引用自定义的属性时,需要使用自定义的xmlns名字。

重写View来实现全新的控件

当Android系统原生控件无法满足我们的需求的时候,我们就可以完全创建一个新的自定义View来实现需要的功能。创建一个自定义View,难点在于绘制控件和实现交互,这也是评价一个自定义View优劣的标准之一。
同时需要继承View类,并重写它的onDraw()、onMeasure()等方法来实现绘制逻辑,同时通过重写onTouchEvent()等触控事件来实现交互逻辑。也可以像实现组合控件方式,通过引入自定义属性,丰富自定义View的可定制性。

弧线展示图

在PPT很多模板中,经常会有比例图,如下图所示。

澳门新葡萄京娱乐场 21

比例图

这个自定义View其实分三个部分,分别是中间的圆形、中间显示的文字和外圈的弧线。为了简单,我们把View的绘制长度直接设置为屏幕的宽度。首先在初始化的时候,设置好绘制三种图形的参数,然后在onDraw()方法中去绘制。代码如下所示。

public class ScaleMap extends View {

    private int mMeasureHeigth;// 控件高度
    private int mMeasureWidth;// 控件宽度
    // 圆形
    private Paint mCirclePaint;
    private float mCircleXY;//圆心坐标
    private float mRadius;//圆形半径
    // 圆弧
    private Paint mArcPaint;
    private RectF mArcRectF;//圆弧的外切矩形
    private float mSweepAngle;//圆弧的角度
    private float mSweepValue;
    // 文字
    private Paint mTextPaint;
    private String mShowText;//文本内容
    private float mShowTextSize;//文本大小

    public ScaleMap(Context context) {
        this(context, null);
    }

    public ScaleMap(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ScaleMap(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    // 如果不用后面的参数,就不需要重构后面的,直接将其内容写在第一个构造方法就可以,父类会自动执行后面的构造方法
    public ScaleMap(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        // 初始化操作
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mMeasureWidth = MeasureSpec.getSize(widthMeasureSpec);//获取控件宽度
        mMeasureHeigth = MeasureSpec.getSize(heightMeasureSpec);//获取控件高度
        setMeasuredDimension(mMeasureWidth, mMeasureHeigth);

        initPaint();  // 画笔中用到了宽高所以在此初始化画笔
    }

    /**
    * 准备画笔,
    */
    private void initPaint() {
        float length = Math.min(mMeasureWidth,mMeasureHeigth);
        // 圆的代码
        mCircleXY = length / 2;// 确定圆心坐标
        mRadius = (float) (length * 0.5 / 2);// 确定半径
        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias(true);// 去锯齿
        mCirclePaint.setColor(getResources().getColor(android.R.color.holo_green_dark));

        // 弧线,需要 指定其椭圆的外接矩形
        // 矩形
        mArcRectF = new RectF((float) (length * 0.1), (float) (length * 0.1), (float)(length * 0.9),(float) (length * 0.9));
        mSweepAngle = (mSweepValue / 100f) * 360f;
        mArcPaint = new Paint();
        mArcPaint.setColor(getResources().getColor(android.R.color.holo_blue_bright));
        mArcPaint.setStrokeWidth((float) (length * 0.1));//圆弧宽度
        mArcPaint.setStyle(Style.STROKE);//圆弧
        // 文字,只需要设置好文字的起始绘制位置即可
        mShowText = "Android Skill";
        mShowTextSize = 50;
        mTextPaint = new Paint();
        mTextPaint.setTextSize(mShowTextSize);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制圆
        canvas.drawCircle(mCircleXY, mCircleXY, mRadius, mCirclePaint);
        // 绘制圆弧,逆时针绘制,角度跟
        canvas.drawArc(mArcRectF, 90, mSweepAngle, false, mArcPaint);
        // 绘制文字
        canvas.drawText(mShowText, 0, mShowText.length(), mCircleXY, mCircleXY + mShowTextSize / 4, mTextPaint);
    }

    // 让调用者来设置不同的状态值,使弧形弧度变化
    public void setSweepValue(float sweepValue) {
        if (sweepValue != 0) {
            mSweepValue = sweepValue;
        } else {
            mSweepValue = 25;
        }
        // 这个方法可以刷新UI
        this.invalidate();
    }

}

可以通过在修改UI后通过调用this.invalidate()方法来重绘。

音频条形图

只演示自定义View的用法,不真实地监听音频输入,随机模拟一些数字即可。先看一下最终实现的效果图,如下图所示。

澳门新葡萄京娱乐场 22

音频图

如果要实现这样的效果,也就是绘制一个个的矩形,每个矩形之间稍微偏移一点距离即可。为了实现动态效果,只要在onDraw()方法中再去调用invalidate()方法通知View进行重绘就可以了。如果直接重绘会刷新太快影响效果,因此,可以使用postInvalidateDelayed(300)来进行延时重绘。代码如下所示。

this.invalidate();
this.postInvalidateDelayed(300);

并且给绘制的Paint对象可以增加一个LinearGradient渐变效果。代码如下所示。

private int mWidth;//控件的宽度
private int mRectWidth;// 矩形的宽度
private int mRectHeight;// 矩形的高度
private Paint mPaint;
private int mRectCount;// 矩形的个数
private int offset = 5;// 偏移
private double mRandom;
private LinearGradient lg;// 渐变

public ScaleMap(Context context) {
    this(context, null);
}

public ScaleMap(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    initPaint();  // 这些要在这里设置,因为渐变效果
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 设置宽高
    setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
}

// 初始化画笔
private void initPaint() {
    mPaint = new Paint();
    mPaint.setColor(Color.GREEN);
    mPaint.setStyle(Paint.Style.FILL);
    mRectCount = 12;
}

//重写onDraw方法
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    for (int i = 0; i < mRectCount; i++) {
        mRandom = Math.random();
        float currentHeight = (int) (mRectHeight * mRandom);
        canvas.drawRect((float) (mWidth * 0.4 / 2 + mRectWidth * i + offset * i), currentHeight,
        (float) (mWidth * 0.4 / 2 + mRectWidth * (i + 1) + offset * i), mRectHeight, mPaint);
    }
    postInvalidateDelayed(300);
}

//重写onSizeChanged方法,给画笔加上渐变
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = getWidth();
    mRectHeight = getHeight();
    mRectWidth = (int) (mWidth * 0.6 / mRectCount);
    lg = new LinearGradient(0, 0, mRectWidth, mRectHeight, Color.GREEN, Color.BLUE, TileMode.CLAMP);
    mPaint.setShader(lg);
}

自定义View一步一步来,从最基本的效果开始,慢慢增加功能,绘制更复杂的效果。

自定义ViewGroup

ViewGroup存在的目的就是为了对其子View进行管理,为其子View添加显示、响应的规则。因此,自定义ViewGroup通常重写onMeasure()方法来对子View进行测量,重写onLayout()方法来确定子View的位置,重写onTouchEvent()方法增加响应事件。
准备实现一个类似Android原生控件ScrollView的自定义ViewGroup,自定义ViewGroup实现ScrollView所具有的上下滑动功能,但是在滑动的过程中增加一个黏性的效果,即当一个子View向上滑动大于一定距离后,松开手指,它将自动向上滑动,显示下
个子View。同理,如果滑动小于一定的距离,松开手指,它将自动滑动到开始的位置。效果图如下所示。

澳门新葡萄京娱乐场 23

自定义ViewGroup效果图

首先让自定义ViewGroup能够实现类似ScrollView的功能。
在ViewGroup能够滚动之前,需要先放置好它的子View。使用遍历的方式来通知子View对自身进行测量,代码如下所示。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int count = getChildCount();  // 返回子View的数量
    for (int i = 0; i < count; ++i) {
        View childView = getChildAt(i);  // 获取子View
        measureChild(childView, widthMeasureSpec, heightMeasureSpec);  // 调用子View的测量方法
    }
}

接下来,就要对子View进行放置位置的设定。让每个子View都显示完整的一屏,这样在滑动的时候,可以比较好地实现后面的效果。在放置子View前,需要确定整个ViewGroup的高度。在本例中,由于让每个子View占一屏的高度,因此整个ViewGroup的高度即子View的个数乘以屏幕的高度,我们通过如下代码来确定整个ViewGroup的高度。

// 设置ViewGroup的高度
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
mlp.height = mScreenHeight * childCount;
setLayoutParams(mlp);

在获取了整个ViewGroup的高度之后,就可以通过遍历来设定每个子View需要放置的位置了,直接通过调用子View的layout()方法,并将具体的位置作为参数传递进去即可,代码如下所示。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount = getChildCount();
    // 设置ViewGroup的高度
    MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
    mlp.height = mScreenHeight * childCount;
    setLayoutParams(mlp);
    for (int i = 0; i < childCount; ++i) {
        View child = getChildAt(i);
        if (child.getVisibility() != View.GONE) {
            child.layout(l, i * mScreenHeight, r, (i + 1) * mScreenHeight);
        }
    }
}

在代码中主要是去修改每个子View的top和bottom这两个属性,让它们能依次排列下来。
通过上面的步骤,就可以将子View放置到ViewGroup中了。但此时的ViewGroup还不能响应任何触控事件,自然也不能滑动,因此我们需要重写onTouchEvent()方法,为ViewGroup添加响应事件。在ViewGroup中添加滑动事件,通常可以使用scrollBy()方法来辅助滑动。在onTouchEvent()的ACTION_MOVE事件中,只要使用scrollBy(0,dy)方法,让手指滑动的时候让ViewGroup中的所有子View也跟着滚动dy即可,计算dy的方法有很多,如下代码就提供了一种思路。

case MotionEvent.ACTION_DOWN:
    mLastY = y;
    break;
case MotionEvent.ACTION_MOVE:
    if (!mScroller.isFinished()) {
        mScroller.abortAnimation();
    }
    int dy = mLastY - y;
    if (getScrollY() < 0 || getScrollY() > getHeight() - mScreenHeight) {
        dy = 0;
    }
    scrollBy(0, dy);
    mLastY = y;
    break;

按如上方法操作就可以实现类似ScrollView的滚动效果了。当然,系统的原生ScrollView有更大的功能,比如滑动的惯性效果等,这些功能可以在后面慢慢添加,这也是一个控件的迭代过程。
最后,我们来实现这个自定义ViewGroup的黏性效果。要实现手指离开后ViewGroup的黏性效果,我们很自然地想到onTouchEvent()的ACTION_UP事件和Scroller类。在ACTION_UP事件中判断手指滑动的距离,如果超过一定距离,则使用Scroller类来平滑移动到下一个子View;如果小于一定距离,则回滚到原来的位置,代码如下所示。

@Override
public boolean onTouchEvent(MotionEvent event) {
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 记录触摸起点
            mStart = getScrollY();
            mLastY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }
            int dy = mLastY - y;
            if (getScrollY() < 0 || getScrollY() > getHeight() - mScreenHeight) {
                dy = 0;
            }
            scrollBy(0, dy);
            mLastY = y;
            break;
        case MotionEvent.ACTION_UP:
            // 记录触摸终点
            mEnd = getScrollY();
            int dScrollY = mEnd - mStart;
            if (dScrollY > 0) {
                if (dScrollY < mScreenHeight / 3) {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, -dScrollY);
                } else {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, mScreenHeight - dScrollY);
                }
            } else {
                if (-dScrollY < mScreenHeight / 3) {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, -dScrollY);
                } else {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, -mScreenHeight - dScrollY);
                }
            }
            break;
    }
    postInvalidate();
    return true;
}

当然,最后不要忘记加上computeScroll()的代码,如下所示。

// 由父视图调用,用来请求子视图根据偏移值 mScrollX,mScrollY 重新绘制
@Override
public void computeScroll() {
    super.computeScroll();
    if(mScroller.computeScrollOffset()){
        scrollTo(0,mScroller.getCurrY());
        postInvalidate();
    }
}

为了易于控制滑屏控制,Android框架提供了
computeScroll()方法去控制这个流程。在绘制View时,会在draw()过程调用该方法。因此,
再配合使用Scroller实例,我们就可以获得当前应该的偏移坐标,手动使View/ViewGroup偏移至该处。

总结

关于自定义控件方面的东西远还这止这些,还需要不断地学习更多的知识,才可制作出优秀的控件。

更多内容戳这里(整理好的各种文集)

发表评论

电子邮件地址不会被公开。 必填项已用*标注