添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
public class ChatInputWidget extends LinearLayout implements OnClickListener {
    private final String TAG = getClass().getSimpleName();
    private ViewPager mEmojiPager;
    private GifEditText mEditText;
    private ImageView mEmojiImageView;
    private Button mSendButton;
    private View pagerView;
    private CircleIndicator circleIndicator;
    private Handler handler = new Handler();
    private List<Emoji> mEmojiList;
    private static final String FILE_NAME = "emoji.txt";
    private static final String GIFT_RES_FILE_PATH = "emoji";
    private static final String ASSETS_FILE_NAME = GIFT_RES_FILE_PATH + "/" + FILE_NAME;
    private final int pagerNum = 10;
    protected Context mContext;
//    protected InputMethodManager inputManager;
    private ChatInputMenuListener mListener;
    private int mToUserId;
    private int mToVipLevel;
    private String toUserName;
    public String getToUserName() {
        return toUserName;
    public void setToUserName(String toUserName) {
        this.toUserName = toUserName;
    public void setToVipLevel(int toVipLevel) {
        this.mToVipLevel = toVipLevel;
    public ChatInputWidget(Context context) {
        super(context);
        if (context instanceof Activity) {
            ((Activity) context).getLayoutInflater().inflate(R.layout.widget_chat_input, this);
        } else {
            inflate(context, R.layout.widget_chat_input, this);
        initView(context);
    public ChatInputWidget(Context context, AttributeSet attrs) {
        super(context, attrs);
        if (context instanceof Activity) {
            ((Activity) context).getLayoutInflater().inflate(R.layout.widget_chat_input, this);
        } else {
            inflate(context, R.layout.widget_chat_input, this);
        initView(context);
    private void initView(Context context) {
        this.mContext = context;
//        inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        mEditText = (GifEditText) findViewById(R.id.chat_et_message);
        mEmojiPager = (ViewPager) findViewById(R.id.chat_vp_face_pager);
        mEmojiImageView = (ImageView) findViewById(R.id.chat_iv_face);
        mSendButton = (Button) findViewById(R.id.chat_bt_send);
        pagerView = findViewById(R.id.rl_chat_pager);
        circleIndicator = (CircleIndicator) findViewById(R.id.indicator_chat_emoji);
    public void init() {
        mSendButton.setOnClickListener(this);
        mEmojiImageView.setOnClickListener(this);
        mEditText.setOnClickListener(this);
        mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    hideEmojicon();
        mEmojiList = getExpressionRes();
        List<View> views = new ArrayList<>();
        int pagerSize;
        if (mEmojiList.size() % pagerNum == 0) {
            pagerSize = mEmojiList.size() / pagerNum;
        } else {
            pagerSize = mEmojiList.size() / pagerNum + 1;
//        LogUtil.w(TAG, "pagerSize === " + pagerSize);
        for (int i = 0; i < pagerSize; i++) {
            if (i == pagerSize - 1) {
                views.add(getGridChildView(mEmojiList.subList(i * pagerNum, mEmojiList.size())));
            } else {
                views.add(getGridChildView(mEmojiList.subList(i * pagerNum, (i + 1) * pagerNum)));
        mEmojiPager.setAdapter(new EmojiPagerAdapter(views));
        circleIndicator.setViewPager(mEmojiPager);
     * 显示或隐藏表情页
    private void toggleEmojicon() {
        if (pagerView.getVisibility() == View.GONE) {
            hideKeyboard();
            handler.postDelayed(new Runnable() {
                public void run() {
                    showEmojicon();
            }, 100);
        } else {
            hideEmojicon();
            handler.postDelayed(new Runnable() {
                public void run() {
                    showKeyboard();
            }, 100);
     * 隐藏表情页
    public void hideEmojicon() {
        pagerView.setVisibility(View.GONE);
        mEmojiImageView.setSelected(false);
    private void showEmojicon() {
        pagerView.setVisibility(View.VISIBLE);
        mEmojiImageView.setSelected(true);
     * 隐藏表情按钮
    public void hideEmojiButton() {
        mEmojiImageView.setVisibility(View.GONE);
    public void setHint(String hint) {
        mEditText.setHint(hint);
    public void setHint(int resid) {
        mEditText.setHint(resid);
    public String getText() {
        return mEditText.getText().toString();
    public void setText(String text) {
        mEditText.setText(text);
        if (text != null) {    //光标移到最后
            mEditText.setSelection(text.length());
    public void setText(int resid) {
        mEditText.setText(resid);
    public void setFocus() {
        mEditText.setFocusable(true);
        mEditText.setFocusableInTouchMode(true);
        mEditText.requestFocus();
        mEditText.requestFocusFromTouch();
        mEditText.postDelayed(new Runnable() {
            @Override
            public void run() {
                AppUtil.showInputMethod(mEditText);
        }, 50);
    public View getEditText() {
        return mEditText;
     * 隐藏软键盘
    public void hideKeyboard() {
        AppUtil.hideInputMethod(mEditText);
    public void showKeyboard() {
        AppUtil.showInputMethod(mEditText);
     * 系统返回键被按时调用此方法
     * @return 返回false表示返回键时扩展菜单栏时打开状态,true则表示按返回键时扩展栏是关闭状态<br/>
     * 如果返回时打开状态状态,会先关闭扩展栏再返回值
    public boolean onBackPressed() {
        if (pagerView.getVisibility() == View.VISIBLE) {    //隐藏表情
            hideEmojicon();
            return false;
        } else {
            return true;
    private void sendMessage() {
        //通过回调通知发送消息
        if (mListener != null) {
            String message = StrUtil.escapeForXML(mEditText.getText().toString());
            if (mListener.onSendMessage(message, mToUserId, mToVipLevel, toUserName)) {
                //清空内容
                mEditText.setText(null);
    public void setToUserId(int userId) {
        mToUserId = userId;
    public int getToUserId() {
        return mToUserId;
     * 清除输入框内容
    public void clearText() {
        mEditText.setText(null);
    public void setChatInputMenuListener(ChatInputMenuListener listener) {
        this.mListener = listener;
    private void insertEmoji(String name) {
        final int pos = mEditText.getSelectionStart();
        final String faceString = "[$" + name + "$]";
        final Editable text = (Editable) mEditText.getText();
        text.insert(pos, faceString);
        final GifDrawable drawable = EmojiHelper.getInstance().getGifDrawable(name);
        if (drawable != null) {
            final ImageSpan gifSpan = new GifImageSpan(drawable);
            text.setSpan(gifSpan, pos, pos + faceString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    private List<Emoji> getExpressionRes() {
        List<Emoji> reslist = new ArrayList<>();
        String emojiJson = FileUtil.readAssetsByName(mContext, ASSETS_FILE_NAME);
        try {
            reslist = parseGiftList(new JSONObject(emojiJson));
        } catch (Exception e) {
            LogUtil.e(TAG, "read local gift error:" + e.toString());
        return reslist;
    private List<Emoji> parseGiftList(JSONObject json) throws Exception {
        List<Emoji> giftList = new ArrayList<>();
        JSONArray jsonArray = json.getJSONArray("emoji");
        if (jsonArray != null) {
            for (int i = 0; i < jsonArray.length(); i++) {
                giftList.add(parseGift(jsonArray.getJSONObject(i)));
        return giftList;
    private Emoji parseGift(JSONObject json) throws Exception {
        Emoji emoji = new Emoji();
        emoji.setId(json.getString("id"));
        emoji.setName(json.getString("name"));
        return emoji;
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.chat_iv_face:
                toggleEmojicon();
                break;
            case R.id.chat_et_message:
                hideEmojicon();
                break;
            case R.id.chat_bt_send:
                sendMessage();
                break;
     * 获取表情的gridview的子view
     * @param
     * @return
    private View getGridChildView(List<Emoji> list) {
        final EmojiGridAdapter expressionAdapter = new EmojiGridAdapter(mContext, list);
        GridView gridView = (GridView) LayoutInflater.from(mContext).inflate(R.layout.layout_emoji_grid, null);
        gridView.setAdapter(expressionAdapter);
        gridView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                try {
                    // 文字输入框可见时,才可输入表情
                    if (mEditText.getVisibility() == View.VISIBLE) {
                        insertEmoji(expressionAdapter.getItem(position).getId());
                } catch (Exception e) {
        return gridView;
    public class EmojiPagerAdapter extends PagerAdapter {
        private List<View> views;
        public EmojiPagerAdapter(List<View> views) {
            this.views = views;
        @Override
        public int getCount() {
            return views.size();
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ((ViewPager) container).addView(views.get(position));
            return views.get(position);
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            ((ViewPager) container).removeView(views.get(position));
    private class EmojiGridAdapter extends QuickAdapter<Emoji> {
        public EmojiGridAdapter(Context context, List<Emoji> objects) {
            super(context, R.layout.item_emoji, objects);
        @Override
        protected void convert(BaseAdapterHelper helper, Emoji item) {
            helper.setText(R.id.emoji_tv_expression, item.getName());
            helper.setImageDrawable(R.id.emoji_iv_expression, EmojiHelper.getInstance().getPngDrawable(item.getId()));
    public int getChatInputLength() {
        if (null != mEditText) {
            return mEditText.getChatInputLength();
        } else {
            return 0;
    public interface ChatInputMenuListener {
         * 发送消息按钮点击
         * @param content  文本内容
         * @param toUserId
        boolean onSendMessage(String content, int toUserId, int toVipLevel, String toUserName);
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              xmlns:skin="http://schemas.android.com/android/skin"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:background="@drawable/skin_label_gray_bg"
              android:orientation="vertical"
              skin:enable="true">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:gravity="center_vertical|right"
        android:orientation="horizontal">
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1.00"
            android:background="@drawable/skin_label_white_bg"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            android:paddingBottom="8dp"
            android:paddingRight="5dp"
            android:paddingTop="8dp"
            skin:enable="true">
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1.00"
                android:gravity="center_vertical|right"
                android:orientation="horizontal">
                <!-- <LinearLayout
                    android:id="@+id/chat_ll_to"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal" >
                    <TextView
                        android:id="@+id/chat_tv_to"
                        android:layout_width="60dp"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center_vertical"
                        android:layout_marginLeft="5dp"
                        android:drawablePadding="5dp"
                        android:drawableRight="@drawable/small_up_arrow"
                        android:ellipsize="end"
                        android:gravity="right"
                        android:singleLine="true"
                        android:text="@string/chat_text_toall"
                        android:textColor="@color/common_blue"
                        android:textSize="14sp" />
                        android:layout_width="1px"
                        android:layout_height="match_parent"
                        android:layout_marginLeft="5dp"
                        android:background="@color/line_view_item" />
                </LinearLayout> -->
                <com.gift.view.GifEditText
                    android:id="@+id/chat_et_message"
                    style="?android:attr/editTextStyle"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="5dp"
                    android:background="@null"
                    android:focusable="true"
                    android:focusableInTouchMode="true"
                    android:gravity="center_vertical"
                    android:hint="@string/chat_hint_message"
                    android:imeOptions="actionNone"
                    android:inputType="textMultiLine"
                    android:maxHeight="100dp"
                    android:maxLength="200"
                    android:textColor="@color/text_content"
                    android:textColorHint="@color/text_prompt_input"
                    android:textSize="14sp"/>
            </LinearLayout>
            <ImageView
                android:id="@+id/chat_iv_face"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:layout_marginLeft="5dp"
                android:src="@drawable/button_selector_chat_face"/>
        </LinearLayout>
        <Button
            android:id="@+id/chat_bt_send"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginLeft="5dp"
            android:background="@drawable/skin_button_bg_white"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:text="@string/chat_text_send"
            android:textColor="@color/button_text_color"
            android:textSize="14sp"
            skin:enable="true"/>
    </LinearLayout>
    <RelativeLayout
        android:id="@+id/rl_chat_pager"
        android:layout_width="match_parent"
        android:layout_height="270dp"
        android:background="@color/white"
        android:paddingBottom="27dp"
        android:visibility="gone"
        <android.support.v4.view.ViewPager
            android:id="@+id/chat_vp_face_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:persistentDrawingCache="animation"
        <com.gift.view.infiniteview.CircleIndicator
            android:id="@+id/indicator_chat_emoji"
            android:layout_width="match_parent"
            android:layout_height="6dp"
            android:layout_alignParentBottom="true"
            app:ci_background="@color/banner_indicator_selected"
            app:ci_gravity="center"
            app:ci_margin="5dp"
            app:ci_mode="solo"
            app:ci_radius="3dp"
            app:ci_selected_background="@color/text_orange"
    </RelativeLayout>
</LinearLayout>
public class GiftWidget extends LinearLayout implements OnClickListener, GiftNumberChangeListener {
    private ViewPager mViewPager;
    private Button mGiveButton;
    private View mNumberView;
    private TextView mBalanceTextView;
    private TextView mNumberTextView;
    private LinearLayout mIndicatorView;
    private TextView pay;
    private final static int GIFT_MAX_ITEM_SIZE = 10;
    private int indicatorSpace = 5;
    private int mNumber;
    private String mSelectedGiftId;
    private GiftNumberPopup mGiftNumberPopup;
    private List<GiftGridAdapter> mAdapterList = new ArrayList<GiftGridAdapter>();
    private GiftGiveClickListener mGiftGiveListener;
    private Context mContext;
    public GiftWidget(Context context) {
        super(context);
        if (context instanceof Activity) {
            ((Activity) context).getLayoutInflater()
                    .inflate(R.layout.widget_gift, this);
        } else {
            inflate(context, R.layout.widget_gift, this);
        initView(context);
    public GiftWidget(Context context, AttributeSet attrs) {
        super(context, attrs);
        if (context instanceof Activity) {
            ((Activity) context).getLayoutInflater()
                    .inflate(R.layout.widget_gift, this);
        } else {
            inflate(context, R.layout.widget_gift, this);
        initView(context);
    private void initView(Context context) {
        this.mContext = context;
        mViewPager = (ViewPager) findViewById(R.id.gift_vp_pager);
        mGiveButton = (Button) findViewById(R.id.gift_bt_give);
        mNumberView = findViewById(R.id.gift_ll_number);
        mBalanceTextView = (TextView) findViewById(R.id.gift_tv_balance);
        mNumberTextView = (TextView) findViewById(R.id.gift_tv_number);
        mIndicatorView = (LinearLayout) findViewById(R.id.gift_ll_indicator);
        pay = (TextView) findViewById(R.id.gift_tv_balance_cz);
        mNumberView.setOnClickListener(this);
        mGiveButton.setOnClickListener(this);
        pay.setOnClickListener(this);
    public void initGift(List<Gift> giftList) {
        if (giftList == null) {
            return;
        mAdapterList.clear();
        if (giftList.size() > 0 && mSelectedGiftId == null) {    //默认选中第一个礼物
            mSelectedGiftId = giftList.get(0).getId();
        final List<View> views = new ArrayList<View>();
        int start = 0;
        while (start < giftList.size()) {
            int count;
            if (giftList.size() - start > GIFT_MAX_ITEM_SIZE) {
                count = GIFT_MAX_ITEM_SIZE;
            } else {
                count = giftList.size() - start;
            List<Gift> list = giftList.subList(start, start + count);
            start = start + count;
            views.add(getGridChildView(list));
        initIndicator(views.size());
        mViewPager.setAdapter(new GiftPagerAdapter(views));
        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                switchIndicator(position);
                //刷新旧的选中状态
                if (mAdapterList.size() > position) {
                    GiftGridAdapter adapter = mAdapterList.get(position);
                    adapter.notifyDataSetChanged();
        switchIndicator(0);
    private void initIndicator(int count) {
        mIndicatorView.removeAllViews();
        for (int i = 0; i < count; i++) {
            ImageView indicator = new ImageView(getContext());
            indicator.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            indicator.setPadding(indicatorSpace, indicatorSpace, indicatorSpace, indicatorSpace);
            indicator.setImageResource(R.drawable.icon_indicator);
            mIndicatorView.addView(indicator);
    private void switchIndicator(int position) {
        for (int i = 0; i < mIndicatorView.getChildCount(); i++) {
            ((ImageView) mIndicatorView.getChildAt(i)).setSelected(i == position ? true : false);
    private View getGridChildView(List<Gift> giftList) {
        final GiftGridAdapter adapter = new GiftGridAdapter(getContext(), 0, giftList);
        mAdapterList.add(adapter);
        GridView gridView = (GridView) LayoutInflater.from(getContext()).inflate(R.layout.layout_gift_grid, null);
        gridView.setAdapter(adapter);
        gridView.setChoiceMode(GridView.CHOICE_MODE_SINGLE);
        gridView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                mSelectedGiftId = adapter.getItem(position).getId();
                adapter.notifyDataSetChanged();
        return gridView;
    public class GiftPagerAdapter extends PagerAdapter {
        private List<View> views;
        public GiftPagerAdapter(List<View> views) {
            this.views = views;
        @Override
        public int getCount() {
            return views.size();
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ((ViewPager) container).addView(views.get(position));
            return views.get(position);
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            ((ViewPager) container).removeView(views.get(position));
    private class GiftGridAdapter extends ArrayAdapter<Gift> {
        public GiftGridAdapter(Context context, int textViewResourceId, List<Gift> giftList) {
            super(context, textViewResourceId, giftList);
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (convertView == null) {
                if (getContext() instanceof Activity) {
                    convertView = ((Activity) getContext()).getLayoutInflater().inflate(R.layout.item_gift, null);
                } else {
                    convertView = View.inflate(getContext(), R.layout.item_gift, null);
                holder = new ViewHolder();
                holder.root = convertView.findViewById(R.id.gift_item_ll_root);
                holder.imageView = (ImageView) convertView.findViewById(R.id.gift_item_iv_gift);
                holder.nameTextView = (TextView) convertView.findViewById(R.id.gift_item_tv_name);
                holder.priceTextView = (TextView) convertView.findViewById(R.id.gift_item_tv_price);
                holder.line = convertView.findViewById(R.id.line);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            if (position > 4) {
                holder.line.setVisibility(View.GONE);
            } else {
                holder.line.setVisibility(View.VISIBLE);
            Gift gift = getItem(position);
            try {
                GiftHelper.getInstance().setPngDrawable(holder.imageView, gift);
                holder.nameTextView.setText(gift.getName());
                holder.priceTextView.setText(gift.getPrice());
            } catch (Exception e) {
                e.printStackTrace();
            if (gift.getId().equals(mSelectedGiftId)) {    //选中
                holder.root.setSelected(true);
            } else {
                holder.root.setSelected(false);
            return convertView;
        private class ViewHolder {
            View root;
            View line;
            ImageView imageView;
            TextView nameTextView;
            TextView priceTextView;
    public void initGiftNumber(Activity activity) {
        if (mGiftNumberPopup == null) {
            mGiftNumberPopup = new GiftNumberPopup(activity);
            mGiftNumberPopup.setNumberChangeListener(this);
        //默认显示数量1
        setGiftNumber(1);
    private void showGiftNumber(View view) {
        mGiftNumberPopup.showPopupWindow(view);
    public void dismissGiftNumber() {
        mGiftNumberPopup.dismiss();
    public void setBalance(String text) {
        mBalanceTextView.setText(text);
    private void setGiftNumber(int number) {
        mNumber = number;
        mNumberTextView.setText(String.valueOf(number));
    public void setGiveGift(OnClickListener listener) {
        mGiveButton.setOnClickListener(listener);
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.gift_bt_give:
                if (mGiftGiveListener != null && mSelectedGiftId != null) {
                    mGiftGiveListener.onGiftGiveClick(Integer.parseInt(mSelectedGiftId), mNumber);
                break;
            case R.id.gift_ll_number:
                showGiftNumber(v);
                break;
            case R.id.gift_tv_balance_cz:
                RechargeActivity.startActivity(mContext);
                mGiftGiveListener.dismiss();
                break;
    @Override
    public void onGiftNumberChange(int number) {
        setGiftNumber(number);
    public void setOnGiftGiveClickListener(GiftGiveClickListener listener) {
        mGiftGiveListener = listener;
    public interface GiftGiveClickListener {
        public void onGiftGiveClick(int giftId, int number);
        public void dismiss();
<?xml version="1.0" encoding="utf-8"?>
<!-- 礼物控件layout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:skin="http://schemas.android.com/android/skin"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:orientation="vertical">
    <com.giflist.view.AdjustHeightViewPager
        android:id="@+id/gift_vp_pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:flipInterval="300"
        android:persistentDrawingCache="animation"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="5dp"
        android:orientation="vertical">
        <LinearLayout
            android:id="@+id/gift_ll_indicator"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="horizontal"/>
            android:layout_width="match_parent"
            android:layout_height="2px"
            android:layout_marginTop="7dp"
            android:background="@color/line_list"
            skin:enable="true"/>
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp">
            <TextView
                android:id="@+id/gift_tv_balance_label"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:layout_centerVertical="true"
                android:text="@string/gift_text_balance"
                android:textColor="@color/text_content"
                android:textSize="14sp"/>
            <TextView
                android:id="@+id/gift_tv_balance"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginRight="5dp"
                android:layout_toLeftOf="@+id/gift_tv_balance_cz"
                android:layout_toRightOf="@+id/gift_tv_balance_label"
                android:ellipsize="end"
                android:singleLine="true"
                android:textColor="@color/text_content"
                android:textSize="14sp"/>
            <TextView
                android:id="@+id/gift_tv_balance_cz"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginRight="@dimen/top_margin"
                android:layout_toLeftOf="@+id/gift_ll_number"
                android:text="@string/recharge_title"
                android:textColor="@color/skin_selector_text_color"
                android:textSize="14sp"
                skin:enable="true"/>
            <LinearLayout
                android:id="@+id/gift_ll_number"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                android:layout_centerVertical="true"
                android:paddingBottom="10dp"
                android:paddingTop="10dp"
                android:layout_marginRight="10dp"
                android:layout_toLeftOf="@+id/gift_bt_give"
                android:background="@drawable/button_gray_selector"
                android:clickable="true"
                android:gravity="right|center_vertical"
                android:orientation="horizontal">
                <TextView
                    android:id="@+id/gift_tv_number"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/skin_selector_text_color"
                    android:textSize="14sp"
                    android:minWidth="50dp"
                    android:gravity="center"
                    android:text="1"
                    skin:enable="true"/>
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:paddingRight="10dp"
                    android:src="@drawable/video_present_number_point_skin_n"/>
            </LinearLayout>
            <Button
                android:id="@+id/gift_bt_give"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:background="@drawable/skin_button_orange_selector"
                android:paddingBottom="10dp"
                android:paddingLeft="25dp"
                android:paddingRight="25dp"
                android:paddingTop="10dp"
                android:text="@string/gift_text_give"
                android:textColor="@color/white"
                skin:enable="true"/>
        </RelativeLayout>
    </LinearLayout>
</LinearLayout>
* Created by Administrator on 2016/4/18. public class TradePasswordPopup extends BasePopupWindow { private Button popupSure, popcancel; private MyGridPasswordView passwordEdit; private LinearLayout tip_text_layout; private TextView incorrect_password; private OnFocus onFocus; private OnClickListener mListener; public TradePasswordPopup(Activity context) { super(context, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); mGravity = Gravity.CENTER; protected boolean getFocusable() { return true; @Override protected void initView(Activity context) { popupSure = (Button) mPopupView.findViewById(R.id.room_password_ok); popcancel = (Button) mPopupView.findViewById(R.id.room_password_cancel); passwordEdit = (MyGridPasswordView) mPopupView.findViewById(R.id.password_edit); incorrect_password = (TextView) mPopupView.findViewById(R.id.tx_tradepassword_error); tip_text_layout = (LinearLayout) mPopupView.findViewById(R.id.passworld_edit); /* incorrect_password.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG); incorrect_password.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (mListener != null) { mListener.onClick(incorrect_password); dismiss(); }); */ popupSure.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { onFocus.focus(passwordEdit.getPassWord()); popcancel.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { dismiss(); passwordEdit.clearPassword(); passwordEdit.forceInputViewGetFocus(); showInputMethod(passwordEdit.getInputView(), 100); public void showPopupWindow() { super.showPopupWindow(); public void clearPassword(){ passwordEdit.clearPassword(); public void setAskPasswordListener(OnClickListener listener) { mListener = listener; public void setListener(OnFocus onFocus) { this.onFocus = onFocus; public interface OnFocus { void focus(String txt); @Override protected Animation getShowAnimation() { return null; @Override protected View getClickToDismissView() { return null; @Override public View getPopupView() { return getPopupViewById(R.layout.popup_trade_password_view); @Override public View getAnimaView() { return mPopupView; @Override public boolean getPopupTransparent() { return false;
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/passworld_edit"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="bottom"
    android:gravity="center"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/popup_dialog_bg"
        android:gravity="center"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:orientation="vertical"
        android:visibility="visible">
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <Button
                android:id="@+id/room_password_cancel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:layout_centerVertical="true"
                android:background="@drawable/white_gray_bottomleft_radius"
                android:paddingBottom="20dp"
                android:paddingTop="20dp"
                android:text="@string/cancel"
                android:textColor="@color/text_b2"
                android:textSize="16sp" />
            <TextView
                android:id="@+id/room_password_prompt_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:padding="10dp"
                android:text="@string/trade_input_trade_password"
                android:textColor="#343434"
                android:textSize="16dp" />
            <Button
                android:id="@+id/room_password_ok"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:background="@drawable/white_gray_bottomright_radius"
                android:paddingBottom="20dp"
                android:paddingTop="20dp"
                android:text="@string/ok"
                android:textColor="@color/bg_title"
                android:textSize="16sp" />
        </RelativeLayout>
        <com.bitvf.bitcoinWallet.view.MyGridPasswordView
            android:id="@+id/password_edit"
            android:layout_width="fill_parent"
            android:layout_height="50dip"
            android:layout_gravity="center_horizontal"
            android:layout_marginBottom="@dimen/verify_code_edit_margin_bottom"
            android:layout_marginLeft="@dimen/verify_code_margin"
            android:layout_marginRight="@dimen/verify_code_margin"
            android:layout_marginTop="30dp"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center"
            app:gpvLineColor="@color/divive_line"
            app:gpvLineWidth="@dimen/verify_code_border_width"
            app:gpvPasswordLength="6"
            app:gpvPasswordTransformation="●"
            app:gpvPasswordType="textPassword"
            app:gpvTextSize="18sp" />
        <TextView
            android:id="@+id/tx_tradepassword_error"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dip"
            android:textSize="14sp"
            android:layout_marginTop="5dip"
            android:layout_marginBottom="40dip"
            android:textColor="@color/text_warn"
            tools:text="密码错误,请重新输入"
    </LinearLayout>
</LinearLayout>
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;
import com.bitvf.bitcoinWallet.base.JJApplication;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class FileLogger {
	private static final String TAG = "FileLogger";
	private static final long MB = 1024 * 1024;
	private static final long LOG_LIMIT = 1 * 1024 * 1024;
	private static final long FREE_SPACE_LIMIT = 5 * 1024 * 1024;
	private static final long FREE_SPACE_TIMER = 20 * 60 * 1000;
	private static final int DAY_LIMIT = 3;	//日志保存的天数
	private static final String INFO_COLOR = "green";
	private static final String ERROR_COLOR = "red";
	private static final String WARN_COLOR = "orange";
	private static final String DEBUG_COLOR = "blue";
	private ExecutorService singleThreadService = Executors.newSingleThreadExecutor();
	private Timer timer = null;
	private boolean spaceAvailable = true;
	void d(String tag, String msg) {
		startThreadService(DEBUG_COLOR, "[" + tag + "]" + msg);
	void e(String tag, String msg) {
		startThreadService(ERROR_COLOR, "[" + tag + "]" + "[ERROR]" + msg);
	void i(String tag, String msg) {
		startThreadService(INFO_COLOR, "[" + tag + "]" + msg);
	void w(String tag, String msg) {
		startThreadService(WARN_COLOR, "[" + tag + "]" + "[WARN]" + msg);
	void v(String tag, String msg) {
		startThreadService(INFO_COLOR, "[" + tag + "]" + msg);
	private void startThreadService(String tag, String msg) {
		File file = getLogRoot();
		if ((file == null) || (!file.exists())) {
			return;
		singleThreadService.execute(getWriterRunnable(tag, msg));
	private Runnable getWriterRunnable(final String tag, final String msg) {
		Runnable runnable = new Runnable() {
			public void run() {
				try {
					File file = getLogRoot();
					if ((file == null) || (!file.exists())) {
						return;
					if ((timer == null) && (!freeSpace())) {
						return;
					FileLogger.this.startCleanUpTimer();
					File availableFile = FileLogger.this.getAvailableFile();
					if (availableFile == null) {
						return;
					boolean available = false;
					if (!availableFile.exists()) {
						try {
							availableFile.createNewFile();
							available = true;
						} catch (IOException e) {
							e.printStackTrace();
					if (!availableFile.exists()) {
						return;
					FileOutputStream fileOutputStream = null;
					try {
						fileOutputStream = new FileOutputStream(availableFile, true);
						if (available) {
							String header = "<header>"
									+ "<meta http-equiv=" + "\""
									+ "Content-Type" + "\"" + " content="
									+ "\"" + "text/html; charset=UTF-8" + "\">"
									+ "</header>";
							fileOutputStream.write(header.getBytes());
						SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
						String strDate = simpleDateFormat.format(new Date());
						String body = "<p><font color =\"" + tag + "\">" + strDate + " " + msg.replaceAll(">", ">").replaceAll("<", "<") + "</p>";
						fileOutputStream.write(body.getBytes());
					} catch (FileNotFoundException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					} finally {
						try {
							if (fileOutputStream != null) {
								fileOutputStream.close();
						} catch (IOException e) {
							e.printStackTrace();
					return;
				} catch (NullPointerException e) {
					e.printStackTrace();
					if(null != e) {
						Log.e(TAG, e.getMessage());
				} catch (Exception e) {
					e.printStackTrace();
					if(null != e) {
						Log.e(TAG, e.getMessage());
		return runnable;
	private File getAvailableFile() {
		File rootFile = getLogRoot();
		if ((rootFile == null) || (!rootFile.exists())) {
			return null;
		removeOldFolders();
		File file = getLogFolder();
		int index = 0;
		List<File> fileList = null;
		File[] files = file.listFiles();
		if ((files != null) && (files.length > 0)) {
			fileList = Arrays.asList(files);
			if (fileList.size() > 1) {
				getSortedFileListByName(fileList);
				files = (File[]) fileList.toArray();
			String name = files[0].getName();
			String cntName = name.substring(0, name.indexOf("."));
			try {
				index = Integer.parseInt(cntName);
			} catch (Exception e) {
				e.printStackTrace();
				Log.e(TAG, "Wrong cntName! : " + cntName);
			if (files[0].length() >= MB / 2) {
				index++;
		String fileName = getLogFileName(index);
		return new File(file, fileName);
	private static String getLogFileName(int index) {
		return String.format("%03d", index) + ".html";
	private File getLogFolder() {
		File rootFile = getLogRoot();
		File file = new File(rootFile, getCurrentDate());
		if (!file.exists()) {
			file.mkdirs();
		return file;
	private void removeFolderBeforeDay(String strDate, int offset) {
		String strBeforeDate = getSpecifiedDayBefore(strDate, offset);
		File rootFile = getLogRoot();
		File file = new File(rootFile, strBeforeDate);
		if (file.exists()) {
			deleteFile(file);
	private void removeOldFolders() {
		File file = getLogRoot();
		if ((file == null) || (!file.exists())) {
			return;
		String strDate = getCurrentDate();
		String strYesterdayDate = getSpecifiedDayBefore(strDate, -1);
		String strBeforeYesterdayDate = getSpecifiedDayBefore(strDate, -2);
		File[] files = file.listFiles();
		if (files == null) {
			return;
		for (File subFile : files) {
			if (subFile.isDirectory()) {
				String fileName = subFile.getName();
				if (!fileName.contains(strDate) && !fileName.contains(strYesterdayDate) && !fileName.contains(strBeforeYesterdayDate)) {
					deleteFile(subFile);
			} else {
				subFile.delete();
	private static void deleteFile(File file) {
		if (file == null) {
			return;
		if (file.exists()) {
			if (file.isDirectory()) {
				File[] files = file.listFiles();
				if (files != null) {
					for (File subFile : files) {
						deleteFile(subFile);
				file.delete();
			} else {
				file.delete();
	private static long getDirSize(File floder) {
		if (floder == null) {
			return 0L;
		if (floder.isDirectory()) {
			long size = 0L;
			File[] files = floder.listFiles();
			if (files != null) {
				for (File subFile : files) {
					size += getDirSize(subFile);
			return size;
		return floder.length();
	private static String getSpecifiedDayBefore(String strDate, int offset) {
		Calendar calendar = Calendar.getInstance();
		Date date = null;
		try {
			date = new SimpleDateFormat("yyyyMMdd").parse(strDate);
		} catch (ParseException e) {
			e.printStackTrace();
		calendar.setTime(date);
		calendar.add(Calendar.DATE, offset);
		String dateTime = new SimpleDateFormat("yyyyMMdd").format(calendar.getTime());
		return dateTime;
	private static File getStorageDir() {
		if (Environment.getExternalStorageState().equals("mounted")) {
			return Environment.getExternalStorageDirectory();
		return Environment.getDataDirectory();
	private static void getSortedFileListByName(List<File> fileList) {
		Collections.sort(fileList, new Comparator<File>() {
			public int compare(File lhs, File rhs) {
				return lhs.getName().compareTo(rhs.getName());
	private boolean spaceIsAlearting() {
		return getCurrentAvailabeSpace() < FREE_SPACE_LIMIT;
	private boolean logSizeAlearting() {
		return getDirSize(getLogRoot()) > LOG_LIMIT;
	boolean freeSpace() {
		File file = getLogRoot();
		if ((file == null) || (!file.exists())) {
			return false;
		if (spaceIsAlearting()) {
			Log.w(TAG, "there is no availabe free space and try to free space");
			freeLogFolder();
			return !spaceIsAlearting();
		checkAndFreeLogFiles();
		return true;
	private void freeLogFolder() {
		deleteFile(getLogRoot());
	private void freeOldFolders() {
		File[] files = getLogRoot().listFiles();
		for (File file : files) {
			if ((file.isDirectory()) && (!file.getName().contains(getCurrentDate()))) {
				deleteFile(file);
			} else {
				file.delete();
	private String getCurrentDate() {
		return new SimpleDateFormat("yyyyMMdd").format(new Date());
	private void freeOldFiles() {
		File[] files = getLogFolder().listFiles();
		if (files != null) {
			List<File> fileList = Arrays.asList(files);
			getSortedFileListByName(fileList);
			if (fileList.size() > 5) {
				int i = fileList.size();
				for (int j = 5; j < i; j++) {
					Log.w(TAG, "try to delete file : " + fileList.get(j).getAbsoluteFile());
					fileList.get(j).delete();
	@TargetApi(18)
	private static long getCurrentAvailabeSpace() {
		StatFs statFs = new StatFs(getStorageDir().getPath());
		if (Build.VERSION.SDK_INT >= 18) {
			return statFs.getAvailableBytes();
		long availableBlocks = statFs.getAvailableBlocks();
		long blockSize = statFs.getBlockSize();
		return availableBlocks * blockSize;
	File getLogRoot() {
		try {
			Context context = JJApplication.getInstance();
			String filePath = "/Android/data/" + context.getPackageName() + "/log/";
			File file = new File(getStorageDir(), filePath);
			synchronized (this) {
				if (!file.exists()) {
					file.mkdirs();
			return file;
		} catch (Throwable e) {
			return null;
	void checkAndFreeLogFiles() {
		if (logSizeAlearting()) {
			Log.w(TAG, "the log size is > 8M, try to free log files");
			freeOldFolders();
			Log.w(TAG, "old folders are deleted");
			if (logSizeAlearting()) {
				Log.w(TAG, "try to delete old log files");
				freeOldFiles();
	private void startCleanUpTimer() {
		synchronized (this) {
			if (timer == null) {
				timer = new Timer();
				TimerTask task = new TimerTask() {
					public void run() {
						singleThreadService.execute(new Runnable() {
							public void run() {
								try {
									File file = getLogRoot();
									if ((file == null) || (!getLogRoot().exists())) {
										return;
									spaceAvailable = freeSpace();
								} catch (NullPointerException e) {
									e.printStackTrace();
									Log.e(TAG, e.getMessage());
								} catch (Exception e) {
									e.printStackTrace();
									Log.e(TAG, e.getMessage());
				timer.schedule(task, FREE_SPACE_TIMER, FREE_SPACE_TIMER);
    public static Bitmap convertViewToBitmap(View view) {
        view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        // view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
        view.buildDrawingCache();
        Bitmap bitmap = view.getDrawingCache();
        return bitmap;
     * 质量压缩法
     * @param image
     * @return
    public static Bitmap compressImage(Bitmap image) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.PNG, 100, baos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
        int options = 100;
        while (baos.toByteArray().length / 1024 > 100) { // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
            baos.reset();// 重置baos即清空baos
            options -= 10;// 每次都减少10
            if (options > 0 && options < 100)
                image.compress(Bitmap.CompressFormat.PNG, options, baos);// 这里压缩options%,把压缩后的数据存放到baos中
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片
        return bitmap;
     * 图片按比例大小压缩方法(根据路径获取图片并压缩)
     * @param srcPath
     * @return
    public static Bitmap getimage(String srcPath) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片,此时把options.inJustDecodeBounds 设回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);// 此时返回bm为空
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
        float hh = 180f;// 这里设置高度为800f
        float ww = 500f;// 这里设置宽度为480f
        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 设置缩放比例
        // 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
        bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
        return bitmap;// compressImage(bitmap);// 压缩好比例大小后再进行质量压缩
     * 图片按比例大小压缩方法(根据路径获取图片并压缩)
     * @param srcPath
     * @return
    public static Bitmap getImageFromSDCard(String srcPath) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片,此时把options.inJustDecodeBounds 设回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);// 此时返回bm为空
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
        float hh = 50f;// 这里设置高度为800f
        float ww = 50f;// 这里设置宽度为480f
        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 设置缩放比例
        // 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
        bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
        return compressImage(bitmap);// 压缩好比例大小后再进行质量压缩
    public static Bitmap getBitmapByPath(String srcPath) {
        return comp(BitmapFactory.decodeFile(srcPath));
     * 图片按比例大小压缩方法(根据Bitmap图片压缩)
     * @param image
     * @return
    public static Bitmap comp(Bitmap image) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.PNG, 100, baos);
        if (baos.toByteArray().length / 1024 > 1024) {// 判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
            baos.reset();// 重置baos即清空baos
            image.compress(Bitmap.CompressFormat.PNG, 50, baos);// 这里压缩50%,把压缩后的数据存放到baos中
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片,此时把options.inJustDecodeBounds 设回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
        float hh = 800f;// 这里设置高度为800f
        float ww = 480f;// 这里设置宽度为480f
        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 设置缩放比例
        newOpts.inPreferredConfig = Config.RGB_565;// 降低图片从ARGB888到RGB565
        // 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
        isBm = new ByteArrayInputStream(baos.toByteArray());
        bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
        return bitmap;// 压缩好比例大小后再进行质量压缩
     * Transfer drawable to bitmap
     * @param drawable
     * @return
    public static Bitmap drawableToBitmap(Drawable drawable) {
        int w = drawable.getIntrinsicWidth();
        int h = drawable.getIntrinsicHeight();
        Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
                : Bitmap.Config.RGB_565;
        Bitmap bitmap = Bitmap.createBitmap(w, h, config);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, w, h);
        drawable.draw(canvas);
        return bitmap;
     * Bitmap to drawable
     * @param bitmap
     * @return
    public static Drawable bitmapToDrawable(Bitmap bitmap) {
        return new BitmapDrawable(bitmap);
     * Input stream to bitmap
     * @param inputStream
     * @return
     * @throws Exception
    public static Bitmap inputStreamToBitmap(InputStream inputStream)
            throws Exception {
        return BitmapFactory.decodeStream(inputStream);
     * Byte transfer to bitmap
     * @param byteArray
     * @return
    public static Bitmap byteToBitmap(byte[] byteArray) {
        if(byteArray!=null){
            if (byteArray.length != 0) {
                return BitmapFactory
                        .decodeByteArray(byteArray, 0, byteArray.length);
            } else {
                return null;
        }else{
            return null;
     * Byte transfer to drawable
     * @param byteArray
     * @return
    public static Drawable byteToDrawable(byte[] byteArray) {
        ByteArrayInputStream ins = null;
        if (byteArray != null) {
            ins = new ByteArrayInputStream(byteArray);
        return Drawable.createFromStream(ins, null);
     * Bitmap transfer to bytes
     * @return
    public static byte[] bitmapToBytes(Bitmap bm) {
        byte[] bytes = null;
        if (bm != null) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
            bytes = baos.toByteArray();
        return bytes;
     * Drawable transfer to bytes
     * @param drawable
     * @return
    public static byte[] drawableToBytes(Drawable drawable) {
        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
        Bitmap bitmap = bitmapDrawable.getBitmap();
        byte[] bytes = bitmapToBytes(bitmap);
        return bytes;
     * Base64 to byte[] //
    // public static byte[] base64ToBytes(String base64) throws IOException {
    // byte[] bytes = Base64.decode(base64);
    // return bytes;
    // /**
    // * Byte[] to base64
    // */
    // public static String bytesTobase64(byte[] bytes) {
    // String base64 = Base64.encode(bytes);
    // return base64;
     * Create reflection images
     * @param bitmap
     * @return
    public static Bitmap createReflectionImageWithOrigin(Bitmap bitmap) {
        final int reflectionGap = 4;
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        Matrix matrix = new Matrix();
        matrix.preScale(1, -1);
        Bitmap reflectionImage = Bitmap.createBitmap(bitmap, 0, h / 2, w,
                h / 2, matrix, false);
        Bitmap bitmapWithReflection = Bitmap.createBitmap(w, (h + h / 2),
                Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmapWithReflection);
        canvas.drawBitmap(bitmap, 0, 0, null);
        Paint deafalutPaint = new Paint();
        canvas.drawRect(0, h, w, h + reflectionGap, deafalutPaint);
        canvas.drawBitmap(reflectionImage, 0, h + reflectionGap, null);
        Paint paint = new Paint();
        LinearGradient shader = new LinearGradient(0, bitmap.getHeight(), 0,
                bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff,
                0x00ffffff, TileMode.CLAMP);
        paint.setShader(shader);
        // Set the Transfer mode to be porter duff and destination in
        paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
        // Draw a rectangle using the paint with our linear gradient
        canvas.drawRect(0, h, w, bitmapWithReflection.getHeight()
                + reflectionGap, paint);
        return bitmapWithReflection;
     * Get rounded corner images
     * @param bitmap
     * @param roundPx
     *            5 10
     * @return
    public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, float roundPx) {
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        Bitmap output = Bitmap.createBitmap(w, h, Config.ARGB_8888);
        Canvas canvas = new Canvas(output);
        final int color = 0xff424242;
        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, w, h);
        final RectF rectF = new RectF(rect);
        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);
        return output;
     * Resize the bitmap
     * @param bitmap
     * @param width
     * @param height
     * @return
    public static Bitmap zoomBitmap(Bitmap bitmap, int width, int height) {
        Bitmap newbmp = null;
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        // 960*1280
        if (w > 960 && h > 1280) {
            Matrix matrix = new Matrix();
            float scaleWidth = ((float) width / w);
            float scaleHeight = ((float) height / h);
            matrix.postScale(scaleWidth, scaleHeight);
            newbmp = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);
            bitmap.recycle();
        } else {
            newbmp = bitmap;
        return newbmp;
     * Resize the drawable
     * @param drawable
     * @param w
     * @param h
     * @return
    public static Drawable zoomDrawable(Drawable drawable, int w, int h) {
        int width = drawable.getIntrinsicWidth();
        int height = drawable.getIntrinsicHeight();
        Bitmap oldbmp = drawableToBitmap(drawable);
        Matrix matrix = new Matrix();
        float sx = ((float) w / width);
        float sy = ((float) h / height);
        matrix.postScale(sx, sy);
        Bitmap newbmp = Bitmap.createBitmap(oldbmp, 0, 0, width, height,
                matrix, true);
        return new BitmapDrawable(newbmp);
     * Get images from SD card by path and the name of image
     * @param photoName
     * @return
    public static Bitmap getPhotoFromSDCard(String path, String photoName) {
        Bitmap photoBitmap = BitmapFactory.decodeFile(path + "/" + photoName
                + ".png");
        if (photoBitmap == null) {
            return null;
        } else {
            return photoBitmap;
    public static Bitmap getPhotoFromSDCard(String path) {
        Bitmap photoBitmap = BitmapFactory.decodeFile(path);
        if (photoBitmap == null) {
            return null;
        } else {
            return photoBitmap;
     * Check the SD card
     * @return
    public static boolean checkSDCardAvailable() {
        return android.os.Environment.getExternalStorageState().equals(
                android.os.Environment.MEDIA_MOUNTED);
     * Get image from SD card by path and the name of image
     * @param path
     * @return
    public static boolean findPhotoFromSDCard(String path, String photoName) {
        boolean flag = false;
        if (checkSDCardAvailable()) {
            File dir = new File(path);
            if (dir.exists()) {
                File folders = new File(path);
                File photoFile[] = folders.listFiles();
                for (int i = 0; i < photoFile.length; i++) {
                    String fileName = photoFile[i].getName().split("\\.")[0];
                    if (fileName.equals(photoName)) {
                        flag = true;
            } else {
                flag = false;
            // File file = new File(path + "/" + photoName + ".jpg" );
            // if (file.exists()) {
            // flag = true;
            // }else {
            // flag = false;
        } else {
            flag = false;
        return flag;
     * Save image to the SD card
     * @param photoBitmap
     * @param photoName
     * @param path
    public static void savePhotoToSDCard(Bitmap photoBitmap, String path,
                                         String photoName) {
        if (checkSDCardAvailable()) {
            File dir = new File(path);
            if (!dir.exists()) {
                dir.mkdirs();
            File photoFile = new File(path, photoName);
            FileOutputStream fileOutputStream = null;
            try {
                fileOutputStream = new FileOutputStream(photoFile);
                if (photoBitmap != null) {
                    if (photoBitmap.compress(Bitmap.CompressFormat.PNG, 100,
                            fileOutputStream)) {
                        fileOutputStream.flush();
                        // fileOutputStream.close();
            } catch (FileNotFoundException e) {
                photoFile.delete();
                e.printStackTrace();
            } catch (IOException e) {
                photoFile.delete();
                e.printStackTrace();
            } finally {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
     * Delete the image from SD card
     * @param path
     *            file:///sdcard/temp.jpg
    public static void deleteAllPhoto(String path) {
        if (checkSDCardAvailable()) {
            File folder = new File(path);
            File[] files = folder.listFiles();
            for (int i = 0; i < files.length; i++) {
                files[i].delete();
    public static void deletePhotoAtPathAndName(String path, String fileName) {
        if (checkSDCardAvailable()) {
            File folder = new File(path);
            File[] files = folder.listFiles();
            for (int i = 0; i < files.length; i++) {
                LogUtil.d("libImageTools", files[i].getName());
                if (files[i].getName().equals(fileName)) {
                    files[i].delete();
    public static Bitmap getBitmap(String localStr){
        Bitmap photoBitmap = BitmapFactory.decodeFile(localStr);
        return photoBitmap;
    public static Bitmap getImagesFromSDCard(String path, String photoName) {
        Bitmap photoBitmap = BitmapFactory.decodeFile(path.concat(photoName));
        if (photoBitmap == null) {
            return null;
        } else {
            return compressBitmap(path.concat(photoName));
    public static Bitmap compressBitmap(String photoPath) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片,此时把options.inJustDecodeBounds 设回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(photoPath, newOpts);// 此时返回bm为空
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
        float hh = 320f;// 这里设置高度为800f
        float ww = 240f;// 这里设置宽度为480f
        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 2;// 设置缩放比例 width,hight设为原来的十分一
        // 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
        bitmap = BitmapFactory.decodeFile(photoPath, newOpts);
        // BitmapFactory.Options options = new BitmapFactory.Options();
        // options.inJustDecodeBounds = true;
        // Bitmap bitmap = BitmapFactory.decodeFile(photoPath, options);
        // //此时返回bm为空
        // options.inJustDecodeBounds = false;
        // //缩放比
        // int be = (int)(options.outHeight / (float)200);
        // if (be <= 0)
        // be = 1;
        // options.inSampleSize = be;
        // //重新读入图片,注意这次要把options.inJustDecodeBounds 设为 false哦
        // bitmap=BitmapFactory.decodeFile(photoPath,options);
        return bitmap;// compressImage(bitmap);
    public static DisplayImageOptions getChatDisplayImageOptions() {
        return mChatDiaplayOptions;
    static DisplayImageOptions mChatDiaplayOptions = new DisplayImageOptions.Builder()
            .cacheInMemory(true)// 设置下载的图片是否缓存在内存中
            .cacheOnDisk(true)// 设置下载的图片是否缓存在SD卡中
            .considerExifParams(true) // 是否考虑JPEG图像EXIF参数(旋转,翻转)
            .imageScaleType(ImageScaleType.EXACTLY_STRETCHED)// 设置图片以如何的编码方式显示
            .bitmapConfig(Bitmap.Config.RGB_565)// 设置图片的解码类型//
            .displayer(new FadeInBitmapDisplayer(500))
            .build();
    public static DisplayImageOptions.Builder getChatDisplayImageOptionsBuilder() {
        return new DisplayImageOptions.Builder().cacheInMemory(true)// 设置下载的图片是否缓存在内存中
                .cacheOnDisk(true)// 设置下载的图片是否缓存在SD卡中
                .considerExifParams(true) // 是否考虑JPEG图像EXIF参数(旋转,翻转)
                .imageScaleType(ImageScaleType.EXACTLY_STRETCHED)// 设置图片以如何的编码方式显示
                .bitmapConfig(Bitmap.Config.RGB_565)// 设置图片的解码类型//
                .displayer(new FadeInBitmapDisplayer(500));
public abstract class TimeDownUtil {
    private final String TAG = getClass().getSimpleName();
    private long mCountDownInterval = 1000;
    private Timer mTimer;
    private TimerTask mTimerTask;
     * 时钟回调的次数
    private int mCount = 0;
     * 倒计时时间,默认间隔单位为秒
     * @param time 毫秒
    public TimeDownUtil(final long time) {
        initTimer(time);
    private void initTimer(long time) {
        mTimer = new Timer();
        mCount = (int) (time / mCountDownInterval);
        LogUtil.w(TAG, "count = " + mCount);
        mTimerTask = new TimerTask() {
            @Override
            public void run() {
                LogUtil.w(TAG, "mCount = " + mCount);
                if (mCount > 0) {
                    mCount--;
                    onTick(mCountDownInterval * mCount);
                } else {
                    onFinish();
                    cancel();
     * 倒计时时间,默认单位为秒
     * @param time
     * @param countDownInterval 间隔时间,1000为1s
    public TimeDownUtil(long time, long countDownInterval) {
        mCountDownInterval = countDownInterval;
        initTimer(time);
    public void start() {
        mTimer.schedule(mTimerTask, 0, mCountDownInterval);
     * Callback fired on regular interval.
     * @param millisUntilFinished 距离结束还有多少时间
    public abstract void onTick(long millisUntilFinished);
     * 倒计时结束的回调
    public abstract void onFinish();
    public void cancel() {
        mTimer.cancel();
        mTimerTask.cancel();
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class ZipUtil {
	private static final int BUFF_SIZE = 1024 * 1024; // 1M Byte
	 * 批量压缩文件(夹)
	 * @param resFileList
	 *            要压缩的文件(夹)列表
	 * @param zipFile
	 *            生成的压缩文件
	 * @throws IOException
	 *             当压缩过程出错时抛出
	public static void zipFiles(Collection<File> resFileList, File zipFile)
			throws IOException {
		FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
		BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
				fileOutputStream, BUFF_SIZE);
		ZipOutputStream zipout = new ZipOutputStream(bufferedOutputStream);
		for (File resFile : resFileList) {
			zipFile(resFile, zipout, "");
		zipout.close();
	 * 批量压缩文件(夹)
	 * @param resFileList
	 *            要压缩的文件(夹)列表
	 * @param zipFile
	 *            生成的压缩文件
	 * @param comment
	 *            压缩文件的注释
	 * @throws IOException
	 *             当压缩过程出错时抛出
	public static void zipFiles(Collection<File> resFileList, File zipFile,
			String comment) throws IOException {
		ZipOutputStream zipout = new ZipOutputStream(new BufferedOutputStream(
				new FileOutputStream(zipFile), BUFF_SIZE));
		for (File resFile : resFileList) {
			zipFile(resFile, zipout, "");
		zipout.setComment(comment);
		zipout.close();
	 * 解压缩一个文件
	 * @param zipFile
	 *            压缩文件
	 * @param folderPath
	 *            解压缩的目标目录
	 * @throws IOException
	 *             当解压缩过程出错时抛出
	public static void upZipFile(File zipFile, String folderPath)
			throws ZipException, IOException {
		File desDir = new File(folderPath);
		if (!desDir.exists()) {
			desDir.mkdirs();
		ZipFile zf = new ZipFile(zipFile);
		for (Enumeration<?> entries = zf.entries(); entries.hasMoreElements();) {
			ZipEntry entry = ((ZipEntry) entries.nextElement());
			InputStream in = zf.getInputStream(entry);
			String str = folderPath + File.separator + entry.getName();
			str = new String(str.getBytes("8859_1"), "GB2312");
			File desFile = new File(str);
			if (!desFile.exists()) {
				File fileParentDir = desFile.getParentFile();
				if (!fileParentDir.exists()) {
					fileParentDir.mkdirs();
				desFile.createNewFile();
			OutputStream out = new FileOutputStream(desFile);
			byte buffer[] = new byte[BUFF_SIZE];
			int realLength;
			while ((realLength = in.read(buffer)) > 0) {
				out.write(buffer, 0, realLength);
			in.close();
			out.close();
	 * 解压文件名包含传入文字的文件
	 * @param zipFile
	 *            压缩文件
	 * @param folderPath
	 *            目标文件夹
	 * @param nameContains
	 *            传入的文件匹配名
	 * @throws ZipException
	 *             压缩格式有误时抛出
	 * @throws IOException
	 *             IO错误时抛出
	public static ArrayList<File> upZipSelectedFile(File zipFile,
			String folderPath, String nameContains) throws ZipException,
			IOException {
		ArrayList<File> fileList = new ArrayList<File>();
		File desDir = new File(folderPath);
		if (!desDir.exists()) {
			desDir.mkdir();
		ZipFile zf = new ZipFile(zipFile);
		for (Enumeration<?> entries = zf.entries(); entries.hasMoreElements();) {
			ZipEntry entry = ((ZipEntry) entries.nextElement());
			if (entry.getName().contains(nameContains)) {
				InputStream in = zf.getInputStream(entry);
				String str = folderPath + File.separator + entry.getName();
				str = new String(str.getBytes("8859_1"), "GB2312");
				// str.getBytes("GB2312"),"8859_1" 输出
				// str.getBytes("8859_1"),"GB2312" 输入
				File desFile = new File(str);
				if (!desFile.exists()) {
					File fileParentDir = desFile.getParentFile();
					if (!fileParentDir.exists()) {
						fileParentDir.mkdirs();
					desFile.createNewFile();
				OutputStream out = new FileOutputStream(desFile);
				byte buffer[] = new byte[BUFF_SIZE];
				int realLength;
				while ((realLength = in.read(buffer)) > 0) {
					out.write(buffer, 0, realLength);
				in.close();
				out.close();
				fileList.add(desFile);
		return fileList;
	 * 获得压缩文件内文件列表
	 * @param zipFile
	 *            压缩文件
	 * @return 压缩文件内文件名称
	 * @throws ZipException
	 *             压缩文件格式有误时抛出
	 * @throws IOException
	 *             当解压缩过程出错时抛出
	public static ArrayList<String> getEntriesNames(File zipFile)
			throws ZipException, IOException {
		ArrayList<String> entryNames = new ArrayList<String>();
		Enumeration<?> entries = getEntriesEnumeration(zipFile);
		while (entries.hasMoreElements()) {
			ZipEntry entry = ((ZipEntry) entries.nextElement());
			entryNames.add(new String(getEntryName(entry).getBytes("GB2312"),
					"8859_1"));
		return entryNames;
	 * 获得压缩文件内压缩文件对象以取得其属性
	 * @param zipFile
	 *            压缩文件
	 * @return 返回一个压缩文件列表
	 * @throws ZipException
	 *             压缩文件格式有误时抛出
	 * @throws IOException
	 *             IO操作有误时抛出
	public static Enumeration<?> getEntriesEnumeration(File zipFile)
			throws ZipException, IOException {
		ZipFile zf = new ZipFile(zipFile);
		return zf.entries();
	 * 取得压缩文件对象的注释
	 * @param entry
	 *            压缩文件对象
	 * @return 压缩文件对象的注释
	 * @throws UnsupportedEncodingException
	public static String getEntryComment(ZipEntry entry)
			throws UnsupportedEncodingException {
		return new String(entry.getComment().getBytes("GB2312"), "8859_1");
	 * 取得压缩文件对象的名称
	 * @param entry
	 *            压缩文件对象
	 * @return 压缩文件对象的名称
	 * @throws UnsupportedEncodingException
	public static String getEntryName(ZipEntry entry)
			throws UnsupportedEncodingException {
		return new String(entry.getName().getBytes("GB2312"), "8859_1");
	 * 压缩文件
	 * @param resFile
	 *            需要压缩的文件(夹)
	 * @param zipout
	 *            压缩的目的文件
	 * @param rootpath
	 *            压缩的文件路径
	 * @throws FileNotFoundException
	 *             找不到文件时抛出
	 * @throws IOException
	 *             当压缩过程出错时抛出
	private static void zipFile(File resFile, ZipOutputStream zipout,
			String rootpath) throws FileNotFoundException, IOException {
		rootpath = rootpath
				+ (rootpath.trim().length() == 0 ? "" : File.separator)
				+ resFile.getName();
		rootpath = new String(rootpath.getBytes("8859_1"), "GB2312");
		if (resFile.isDirectory()) {
			File[] fileList = resFile.listFiles();
			for (File file : fileList) {
				zipFile(file, zipout, rootpath);
		} else {
			byte buffer[] = new byte[BUFF_SIZE];
			BufferedInputStream in = new BufferedInputStream(
					new FileInputStream(resFile), BUFF_SIZE);
			ZipEntry zipEntry = new ZipEntry(rootpath);
			zipEntry.setTime(resFile.lastModified());
			zipout.putNextEntry(zipEntry);
			int realLength;
			while ((realLength = in.read(buffer)) != -1) {
				zipout.write(buffer, 0, realLength);
			in.close();
			zipout.flush();
			zipout.closeEntry();

import java.math.BigDecimal;

/**
* 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精
* 确的浮点数运算,包括加减乘除和四舍五入。
*/
public class Arith { //默认除法运算精度
private static final int DEF_DIV_SCALE = 10; //这个类不能实例化

private Arith() {
}

/**
* 提供精确的加法运算。
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}

public static String add(String v1, String v2){
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).stripTrailingZeros().toPlainString();
}

/**
* 提供精确的减法运算。
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}

public static String sub(String v1, String v2){
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).stripTrailingZeros().toPlainString();
}

/**
* 提供精确的乘法运算。
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}

public static String mul(String v1, String v2){
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).stripTrailingZeros().toPlainString();
}

/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入。
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}

/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}


public static String div(String v1, String v2, int scale){
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString();
}

/**
* 提供精确的小数位四舍五入处理。
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
};

public class MenuItemView extends RelativeLayout{
private TextView mTextView;
private TextView mLeftTextView;
private TextView mRightTextView;
private ImageView mImageView;
private ImageView mArrowIcon;
private CharSequence mTitle;
private CharSequence mRightText;
private int mTextColor;
private int mTextSize;
private int mIconResId;
private boolean mArrowVisibility;

public MenuItemView(Context context) {
    super(context);
    inflate(context, R.layout.widget_menu_item, this);
    initView();
public MenuItemView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
public MenuItemView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    inflate(context, R.layout.widget_menu_item, this);
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MenuItemView, defStyle, 0);
    mTitle = typedArray.getText(R.styleable.MenuItemView_text);
    mRightText = typedArray.getText(R.styleable.MenuItemView_rightText);
    mTextColor = typedArray.getColor(R.styleable.MenuItemView_textColor, 0);
    mTextSize = typedArray.getDimensionPixelSize(R.styleable.MenuItemView_textSize, 0);
    mIconResId = typedArray.getResourceId(R.styleable.MenuItemView_imageSrc, 0);
    mArrowVisibility = typedArray.getBoolean(R.styleable.MenuItemView_arrowVibility, true);
    typedArray.recycle();
    initView();
public void setArrowVisibility(int visibility) {
    mArrowIcon.setVisibility(visibility);
private void initView() {
    mTextView = (TextView) findViewById(R.id.menu_item_tv_title);
    mLeftTextView = (TextView) findViewById(R.id.menu_item_tv_left);
    mRightTextView = (TextView) findViewById(R.id.menu_item_tv_right);
    mImageView = (ImageView) findViewById(R.id.menu_item_iv_icon);
    mArrowIcon = (ImageView) findViewById(R.id.menu_item_iv_arrow);
    mTextView.setText(mTitle);
    if (mIconResId != 0) {
        mImageView.setImageResource(mIconResId);
    if (mTextColor != 0) {
        mTextView.setTextColor(mTextColor);
    if (mTextSize != 0) {
        mTextView.setTextSize(mTextSize);
    if (!mArrowVisibility) {
        mArrowIcon.setVisibility(View.GONE);
    //右边文字
    if (mRightText != null) {
        mRightTextView.setVisibility(View.VISIBLE);
        mRightTextView.setText(mRightText);
    this.setClickable(true);
public void setText(String text) {
    mTextView.setText(text);
public void setLeftText(String text) {
    //左边文字
    mLeftTextView.setVisibility(View.VISIBLE);
    mLeftTextView.setText(text);
public void setRightText(int resId) {
    //右边文字
    mRightTextView.setVisibility(View.VISIBLE);
    mRightTextView.setText(resId);
public void setRightText(String text) {
    //右边文字
    mRightTextView.setVisibility(View.VISIBLE);
    mRightTextView.setText(text);
public void setRightTextColor(int resId) {
    //右边文字
    mRightTextView.setVisibility(View.VISIBLE);
    mRightTextView.setTextColor(resId);
public void setRightTextVisibility(int visibility) {
    mRightTextView.setVisibility(visibility);
public void hideLeftText() {
    mLeftTextView.setVisibility(View.GONE);
<?xml version="1.0" encoding="utf-8"?><!-- 菜单item -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:paddingBottom="10dp"
    android:paddingTop="10dp">
    <ImageView
        android:id="@+id/menu_item_iv_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true" />
    <TextView
        android:id="@+id/menu_item_tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="@dimen/content_margin"
        android:layout_toRightOf="@+id/menu_item_iv_icon"
        android:textColor="@color/text_black"
        android:textSize="@dimen/text_size_4x" />
    <TextView
        android:id="@+id/menu_item_tv_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_toRightOf="@+id/menu_item_tv_title"
        android:textColor="@color/text_black"
        android:textSize="16sp"
        android:visibility="gone" />
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:gravity="center_vertical">
        <TextView
            android:id="@+id/menu_item_tv_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:textColor="@color/text_black"
            android:textSize="16sp"
            android:visibility="gone" />
        <ImageView
            android:id="@+id/menu_item_iv_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:src="@drawable/home_point_icon" />
    </LinearLayout>
</RelativeLayout>

public class RedPointTextView extends TextView {
private static final int DEFAULT_MIN_SIZE_DIP = 18; //最小尺寸
private static final int DEFAULT_TB_PADDING_DIP = 3; //上下边距
private static final int DEFAULT_LR_PADDING_DIP = 6; //左右边距
private static final int DEFAULT_TEXT_SIZE = 11; //dp
private static final int DEFAULT_TEXT_COLOR = Color.WHITE;
private int mLeftPadding;
private int mTopPadding;
private int mMinSize;

public RedPointTextView(Context context) {
    super(context);
    init();
public RedPointTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
public RedPointTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
private void init() {
    mLeftPadding = dipToPixels(DEFAULT_LR_PADDING_DIP);
    mTopPadding = dipToPixels(DEFAULT_TB_PADDING_DIP);
    mMinSize = dipToPixels(DEFAULT_MIN_SIZE_DIP);
    setIncludeFontPadding(false);
    setGravity(Gravity.CENTER);
    setMinWidth(mMinSize);
    setMinHeight(mMinSize);
    setTextColor(DEFAULT_TEXT_COLOR);
    setTextSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_TEXT_SIZE);
    setBackgroundResource(R.drawable.shape_oval_pink);
    setVisibility(View.GONE);
private int dipToPixels(int dip) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
public void setUnreadCount(int count) {
    if (count <= 0) {
        setVisibility(GONE);
    } else {
        setVisibility(GONE);
        if (count < 10) {
            setPadding(0, 0, 0, 0);
        } else {
            setPadding(mLeftPadding, mTopPadding, mLeftPadding, mTopPadding);
        if (count > 99) {
            setText("99+");
        } else {
            setText(String.valueOf(count));

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.text.Html;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.method.Touch;
import android.text.style.ClickableSpan;
import android.text.style.ImageSpan;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.Request;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import pl.droidsonroids.gif.GifDrawable;

public class RichTextView extends GifSpanTextView implements Drawable.Callback, View.OnAttachStateChangeListener {

private static Pattern IMAGE_TAG_PATTERN = Pattern.compile("\\<img(.*?)\\>");
private static Pattern IMAGE_WIDTH_PATTERN = Pattern.compile("width=\"(.*?)\"");
private static Pattern IMAGE_HEIGHT_PATTERN = Pattern.compile("height=\"(.*?)\"");
private static Pattern IMAGE_SRC_PATTERN = Pattern.compile("src=\"(.*?)\"");
private static Pattern IMAGE_BIG_SRC_PATTERN = Pattern.compile("bigsrc=\"(.*?)\"");
private static Pattern IMAGE_HREF_PATTERN = Pattern.compile("href=\"(.*?)\"");
private Drawable placeHolder, errorImage;//占位图,错误图
private OnImageClickListener onImageClickListener;//图片点击回调
private OnURLClickListener onURLClickListener;//超链接点击回调
//    private HashSet<Target> targets;

// private HashSet targets;
private HashMap mImages;
private HashMap mBigImages;
private ImageFixListener mImageFixListener;
private int d_w = 200;
private int d_h = 100;
private boolean isScroll = true;

private String imageHost;
private boolean dontConsumeNonUrlClicks = true;
private boolean linkHit;
private Context mContext;
private GlideImageGeter glideImageGeter;
public RichTextView(Context context) {
    this(context, null);
public RichTextView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
public RichTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context, attrs);
private void init(Context context, AttributeSet attrs) {
    mContext = context;
 //   targets = new HashSet<>();
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RichTextView);
    placeHolder = typedArray.getDrawable(R.styleable.RichTextView_placeHolder);
    errorImage = typedArray.getDrawable(R.styleable.RichTextView_errorImage);
    d_w = typedArray.getDimensionPixelSize(R.styleable.RichTextView_defaultWidth, Utils.Dp2Px(context, d_w));
    d_h = typedArray.getDimensionPixelSize(R.styleable.RichTextView_defaultHeight, Utils.Dp2Px(context, d_h));
    isScroll = typedArray.getBoolean(R.styleable.RichTextView_isScroll, isScroll);
    if (placeHolder == null) {
        placeHolder = new ColorDrawable(Color.GRAY);
    placeHolder.setBounds(0, 0, d_w, d_h);
    if (errorImage == null) {
        errorImage = new ColorDrawable(Color.GRAY);
    errorImage.setBounds(0, 0, d_w, d_h);
    typedArray.recycle();
 * 设置富文本
 * @param text 富文本
public void setRichText(String text) {
    try {
        glideImageGeter = new GlideImageGeter(getContext(), this);
     //   targets.clear();
        matchImages(text);
        if (text != null) {
            text = text.replaceAll(" ", "\t");  //替换空格
        Spanned spanned = Html.fromHtml(text, glideImageGeter, null);
        SpannableStringBuilder spannableStringBuilder;
        if (spanned instanceof SpannableStringBuilder) {
            spannableStringBuilder = (SpannableStringBuilder) spanned;
        } else {
            spannableStringBuilder = new SpannableStringBuilder(spanned);
        // 处理图片得点击事件
        ImageSpan[] imageSpans = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), ImageSpan.class);
        final List<String> imageUrls = new ArrayList<>();
        for (int i = 0, size = imageSpans.length; i < size; i++) {
            ImageSpan imageSpan = imageSpans[i];
            String imageUrl = imageSpan.getSource();
            String bigImageUrl = mBigImages.get(imageUrl);
            if (!TextUtils.isEmpty(bigImageUrl)) {
                imageUrl = bigImageUrl;
            int start = spannableStringBuilder.getSpanStart(imageSpan);
            int end = spannableStringBuilder.getSpanEnd(imageSpan);
            imageUrl = imageUrl.startsWith("http") ? imageUrl : imageHost + imageUrl;
            if (imageUrl.endsWith(".gif") || imageUrl.endsWith(".bmp") || imageUrl.endsWith(".jpg") || imageUrl.endsWith(".jpeg") || imageUrl.endsWith(".JPG") || imageUrl.endsWith(".JPEG") || imageUrl.endsWith(".png") || imageUrl.endsWith(".PNG")) {
                imageUrls.add(imageUrl);
            final int finalI = i;
            ClickableSpan clickableSpan = new ClickableSpan() {
                @Override
                public void onClick(View widget) {
                    if (onImageClickListener != null) {
                        onImageClickListener.imageClicked(imageUrls, finalI);
            ClickableSpan[] clickableSpans = spannableStringBuilder.getSpans(start, end, ClickableSpan.class);
            if (clickableSpans != null && clickableSpans.length != 0) {
                for (ClickableSpan cs : clickableSpans) {
                    spannableStringBuilder.removeSpan(cs);
            spannableStringBuilder.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        // 处理超链接点击事件
        URLSpan[] urlSpans = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), URLSpan.class);
        for (int i = 0, size = urlSpans == null ? 0 : urlSpans.length; i < size; i++) {
            URLSpan urlSpan = urlSpans[i];
            int start = spannableStringBuilder.getSpanStart(urlSpan);
            int end = spannableStringBuilder.getSpanEnd(urlSpan);
            spannableStringBuilder.removeSpan(urlSpan);
            spannableStringBuilder.setSpan(new CallableURLSpan(urlSpan.getURL(), onURLClickListener), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //表情转义
        final Matcher matcher = EmojiHelper.getInstance().getEmojiMatcher(spanned.toString());
        while (matcher.find()) {
            final String name = matcher.group(1);
            final GifDrawable drawable = EmojiHelper.getInstance().getGifDrawable(name);
            if (drawable != null) {
                final ImageSpan gifSpan = new GifImageSpan(drawable);
                spannableStringBuilder.setSpan(gifSpan, matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        super.setText(spanned);
        if (isScroll) {
            setMovementMethod(LocalLinkMovementMethod.getInstance());    //解决ListView里TextView设置LinkMovementMethod后导致其ItemClick失效的问题
        setLongClickable(false);    //屏蔽长按事件,防止部分手机Spannable长按时崩溃
    } catch (Exception ex) {
        ex.printStackTrace();
    private void addTarget(ImageTarget target) {
targets.add(target);
* 从文本中拿到标签,并获取图片url和宽高
private void matchImages(String text) {
mImages = new HashMap<>();
mBigImages = new HashMap<>();
ImageHolder holder;
Matcher imageMatcher, srcMatcher, bigSrcMatcher, widthMatcher, heightMatcher, hrefMatcher;
int position = 0;
hrefMatcher = IMAGE_HREF_PATTERN.matcher(text);
String hrefImageUrl = null;
if (hrefMatcher.find()) {
hrefImageUrl = getTextBetweenQuotation(hrefMatcher.group().trim().substring(4));
imageMatcher = IMAGE_TAG_PATTERN.matcher(text);
while (imageMatcher.find()) {
String image = imageMatcher.group().trim();
srcMatcher = IMAGE_SRC_PATTERN.matcher(image);
String src = null;
if (srcMatcher.find()) {
src = getTextBetweenQuotation(srcMatcher.group().trim().substring(4));
if (TextUtils.isEmpty(src)) {
continue;
bigSrcMatcher = IMAGE_BIG_SRC_PATTERN.matcher(image);
String bigSrc = null;
if (bigSrcMatcher.find()) {
bigSrc = getTextBetweenQuotation(bigSrcMatcher.group().trim().substring(4));
if (TextUtils.isEmpty(bigSrc)) {
if (!TextUtils.isEmpty(hrefImageUrl)) {
bigSrc = hrefImageUrl;
} else {
bigSrc = src;
        holder = new ImageHolder(src, bigSrc, position);
        widthMatcher = IMAGE_WIDTH_PATTERN.matcher(image);
        if (widthMatcher.find()) {
            holder.width = parseStringToInteger(getTextBetweenQuotation(widthMatcher.group().trim().substring(6)));
        heightMatcher = IMAGE_HEIGHT_PATTERN.matcher(image);
        if (heightMatcher.find()) {
            holder.height = parseStringToInteger(getTextBetweenQuotation(heightMatcher.group().trim().substring(6)));
        mImages.put(holder.src, holder);
        mBigImages.put(holder.src, holder.bigSrc);
        position++;
private int parseStringToInteger(String integerStr) {
    int result = -1;
    if (!TextUtils.isEmpty(integerStr)) {
        try {
            result = Integer.parseInt(integerStr);
        } catch (Exception e) {
            e.printStackTrace();
    return result;
 * 从双引号之间取出字符串
@Nullable
private static String getTextBetweenQuotation(String text) {
    Pattern pattern = Pattern.compile("\"(.*?)\"");
    Matcher matcher = pattern.matcher(text);
    if (matcher.find()) {
        return matcher.group(1);
    return null;
public static class LocalLinkMovementMethod extends LinkMovementMethod {
    static LocalLinkMovementMethod sInstance;
    public static LocalLinkMovementMethod getInstance() {
        if (sInstance == null)
            sInstance = new LocalLinkMovementMethod();
        return sInstance;
    @Override
    public boolean onTouchEvent(TextView widget,
                                Spannable buffer, MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_UP ||
                action == MotionEvent.ACTION_DOWN) {
            int x = (int) event.getX();
            int y = (int) event.getY();
            x -= widget.getTotalPaddingLeft();
            y -= widget.getTotalPaddingTop();
            x += widget.getScrollX();
            y += widget.getScrollY();
            Layout layout = widget.getLayout();
            int line = layout.getLineForVertical(y);
            int off = layout.getOffsetForHorizontal(line, x);
            ClickableSpan[] link = buffer.getSpans(
                    off, off, ClickableSpan.class);
            if (link.length != 0) {
                if (action == MotionEvent.ACTION_UP) {
                    link[0].onClick(widget);
                } else if (action == MotionEvent.ACTION_DOWN) {
                    Selection.setSelection(buffer,
                            buffer.getSpanStart(link[0]),
                            buffer.getSpanEnd(link[0]));
                if (widget instanceof RichTextView) {
                    ((RichTextView) widget).linkHit = true;
                return true;
            } else {
                Selection.removeSelection(buffer);
                Touch.onTouchEvent(widget, buffer, event);
                return false;
        return Touch.onTouchEvent(widget, buffer, event);
private static final class URLDrawable extends Drawable {
    private Drawable drawable;
    @Override
    public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
    @Override
    public int getOpacity() {
        return 0;
    @Override
    public void draw(Canvas canvas) {
        if (drawable != null)
            drawable.draw(canvas);
    public void setDrawable(Drawable drawable) {
        this.drawable = drawable;
private static class CallableURLSpan extends URLSpan {
    private OnURLClickListener onURLClickListener;
    public CallableURLSpan(String url, OnURLClickListener onURLClickListener) {
        super(url);
        this.onURLClickListener = onURLClickListener;
    @SuppressWarnings("unused")
    public CallableURLSpan(Parcel src, OnURLClickListener onURLClickListener) {
        super(src);
        this.onURLClickListener = onURLClickListener;
    @Override
    public void onClick(View widget) {
        if (onURLClickListener != null && onURLClickListener.urlClicked(getURL())) {
            return;
        super.onClick(widget);
public static class ImageHolder {
    public static final int DEFAULT = 0;
    public static final int CENTER_CROP = 1;
    public static final int CENTER_INSIDE = 2;
    @IntDef({DEFAULT, CENTER_CROP, CENTER_INSIDE})
    public @interface ScaleType {
    private final String src;
    private final String bigSrc;
    private final int position;
    private int width = -1, height = -1;
    private int scaleType = DEFAULT;
    public ImageHolder(String src, String bigSrc, int position) {
        this.src = src;
        this.bigSrc = bigSrc;
        this.position = position;
    public String getSrcUrl() {
        return src;
    public String getBigSrcUrl() {
        return bigSrc;
    public int getHeight() {
        return height;
    public int getWidth() {
        return width;
    public void setWidth(int width) {
        this.width = width;
    public void setHeight(int height) {
        this.height = height;
    @ScaleType
    public int getScaleType() {
        return scaleType;
    public void setScaleType(@ScaleType int scaleType) {
        this.scaleType = scaleType;
@SuppressWarnings("unused")
public ImageHolder getImageHolder(String url) {
    return mImages.get(url);
@SuppressWarnings("unused")
public void setPlaceHolder(Drawable placeHolder) {
    this.placeHolder = placeHolder;
    this.placeHolder.setBounds(0, 0, d_w, d_h);
@SuppressWarnings("unused")
public void setErrorImage(Drawable errorImage) {
    this.errorImage = errorImage;
    this.errorImage.setBounds(0, 0, d_w, d_h);
public void setOnImageClickListener(OnImageClickListener onImageClickListener) {
    this.onImageClickListener = onImageClickListener;
public void setImageFixListener(ImageFixListener mImageFixListener) {
    this.mImageFixListener = mImageFixListener;
 * 设置超链接点击回调事件(需在setRichText方法之前调用)
 * @param onURLClickListener 回调
public void setOnURLClickListener(OnURLClickListener onURLClickListener) {
    this.onURLClickListener = onURLClickListener;
@Override
public void onViewAttachedToWindow(View v) {
@Override
public void onViewDetachedFromWindow(View v) {
    glideImageGeter.recycle();
public interface OnImageClickListener {
     * 图片被点击后的回调方法
     * @param imageUrls 本篇富文本内容里的全部图片
     * @param position  点击处图片在imageUrls中的位置
    void imageClicked(List<String> imageUrls, int position);
public interface OnURLClickListener {
     * 超链接点击得回调方法
     * @param url 点击得url
     * @return true:已处理,false:未处理(会进行默认处理)
    boolean urlClicked(String url);
public interface ImageFixListener {
     * 修复图片尺寸的方法
     * @param holder ImageHolder对象
    void onFix(ImageHolder holder);
public void configure(String imageHostPrefix) {
    imageHost = imageHostPrefix;


import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
* <p>
* 身份证合法性校验
* </p>
*
* <pre>
* --15位身份证号码:第7、8位为出生年份(两位数),第9、10位为出生月份,第11、12位代表出生日期,第15位代表性别,奇数为男,偶数为女。
* --18位身份证号码:第7、8、9、10位为出生年份(四位数),第11、第12位为出生月份,第13、14位代表出生日期,第17位代表性别,奇数为男,偶数为女。
* 最后一位为校验位
* </pre>
*
*
@author
*/

public class IdcardValidator {
/**
* <pre>
* 省、直辖市代码表:
* 11 : 北京 12 : 天津 13 : 河北 14 : 山西 15 : 内蒙古
* 21 : 辽宁 22 : 吉林 23 : 黑龙江 31 : 上海 32 : 江苏
* 33 : 浙江 34 : 安徽 35 : 福建 36 : 江西 37 : 山东
* 41 : 河南 42 : 湖北 43 : 湖南 44 : 广东 45 : 广西 46 : 海南
* 50 : 重庆 51 : 四川 52 : 贵州 53 : 云南 54 : 西藏
* 61 : 陕西 62 : 甘肃 63 : 青海 64 : 宁夏 65 : 新疆
* 71 : 台湾
* 81 : 香港 82 : 澳门
* 91 : 国外
* </pre>
*/
private static String cityCode[] = { "11", "12", "13", "14", "15", "21",
"22", "23", "31", "32", "33", "34", "35", "36", "37", "41", "42",
"43", "44", "45", "46", "50", "51", "52", "53", "54", "61", "62",
"63", "64", "65", "71", "81", "82", "91" };

/**
* 每位加权因子
*/
private static int power[] = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5,
8, 4, 2 };

/**
* 验证所有的身份证的合法性
*
*
@param idcard
* 身份证
*
@return 合法返回true,否则返回false
*/
public static boolean isValidatedAllIdcard(String idcard) {
if (idcard == null || "".equals(idcard)) {
return false;
}
if (idcard.length() == 15) {
return validate15IDCard(idcard);
}
return validate18Idcard(idcard);
}

/**
* <p>
* 判断18位身份证的合法性
* </p>
* 根据〖中华人民共和国国家标准GB11643-1999〗中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。
* 排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
* <p>
* 顺序码: 表示在同一地址码所标识的区域范围内,对同年、同月、同 日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配 给女性。
* </p>
* <p>
* 1.前1、2位数字表示:所在省份的代码; 2.第3、4位数字表示:所在城市的代码; 3.第5、6位数字表示:所在区县的代码;
* 4.第7~14位数字表示:出生年、月、日; 5.第15、16位数字表示:所在地的派出所的代码;
* 6.第17位数字表示性别:奇数表示男性,偶数表示女性;
* 7.第18位数字是校检码:也有的说是个人信息码,一般是随计算机的随机产生,用来检验身份证的正确性。校检码可以是0~9的数字,有时也用x表示。
* </p>
* <p>
* 第十八位数字(校验码)的计算方法为: 1.将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7 9 10 5 8 4
* 2 1 6 3 7 9 10 5 8 4 2
* </p>
* <p>
* 2.将这17位数字和系数相乘的结果相加。
* </p>
* <p>
* 3.用加出来和除以11,看余数是多少
* </p>
* 4.余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3
* 2。
* <p>
* 5.通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2。
* </p>
*
*
@param idcard
* @return
*/
public static boolean validate18Idcard(String idcard) {
if (idcard == null) {
return false;
}

// 非18位为假
if (idcard.length() != 18) {
return false;
}
// 获取前17位
String idcard17 = idcard.substring(0, 17);

// 前17位全部为数字
if (!isDigital(idcard17)) {
return false;
}

String provinceid = idcard.substring(0, 2);
// 校验省份
if (!checkProvinceid(provinceid)) {
return false;
}

// 校验出生日期
String birthday = idcard.substring(6, 14);

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

try {
Date birthDate = sdf.parse(birthday);
String tmpDate = sdf.format(birthDate);
if (!tmpDate.equals(birthday)) {// 出生年月日不正确
return false;
}

} catch (ParseException e1) {

return false;
}

// 获取第18位
String idcard18Code = idcard.substring(17, 18);

char c[] = idcard17.toCharArray();

int bit[] = converCharToInt(c);

int sum17 = 0;

sum17 = getPowerSum(bit);

// 将和值与11取模得到余数进行校验码判断
String checkCode = getCheckCodeBySum(sum17);
if (null == checkCode) {
return false;
}
// 将身份证的第18位与算出来的校码进行匹配,不相等就为假
if (!idcard18Code.equalsIgnoreCase(checkCode)) {
return false;
}

return true;
}

/**
* 校验15位身份证
*
* <pre>
* 只校验省份和出生年月日
* </pre>
*
*
@param idcard
* @return
*/
public static boolean validate15IDCard(String idcard) {
if (idcard == null) {
return false;
}
// 非15位为假
if (idcard.length() != 15) {
return false;
}

// 15全部为数字
if (!isDigital(idcard)) {
return false;
}

String provinceid = idcard.substring(0, 2);
// 校验省份
if (!checkProvinceid(provinceid)) {
return false;
}

String birthday = idcard.substring(6, 12);

SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");

try {
Date birthDate = sdf.parse(birthday);
String tmpDate = sdf.format(birthDate);
if (!tmpDate.equals(birthday)) {// 身份证日期错误
return false;
}

} catch (ParseException e1) {

return false;
}

return true;
}

/**
* 将15位的身份证转成18位身份证
*
*
@param idcard
* @return
*/
public static String convertIdcarBy15bit(String idcard) {
if (idcard == null) {
return null;
}

// 非15位身份证
if (idcard.length() != 15) {
return null;
}

// 15全部为数字
if (!isDigital(idcard)) {
return null;
}

String provinceid = idcard.substring(0, 2);
// 校验省份
if (!checkProvinceid(provinceid)) {
return null;
}

String birthday = idcard.substring(6, 12);

SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");

Date birthdate = null;
try {
birthdate = sdf.parse(birthday);
String tmpDate = sdf.format(birthdate);
if (!tmpDate.equals(birthday)) {// 身份证日期错误
return null;
}

} catch (ParseException e1) {
return null;
}

Calendar cday = Calendar.getInstance();
cday.setTime(birthdate);
String year = String.valueOf(cday.get(Calendar.YEAR));

String idcard17 = idcard.substring(0, 6) + year + idcard.substring(8);

char c[] = idcard17.toCharArray();
String checkCode = "";

// 将字符数组转为整型数组
int bit[] = converCharToInt(c);

int sum17 = 0;
sum17 = getPowerSum(bit);

// 获取和值与11取模得到余数进行校验码
checkCode = getCheckCodeBySum(sum17);

// 获取不到校验位
if (null == checkCode) {
return null;
}
// 将前17位与第18位校验码拼接
idcard17 += checkCode;
return idcard17;
}

/**
* 校验省份
*
*
@param provinceid
* @return 合法返回TRUE,否则返回FALSE
*/
private static boolean checkProvinceid(String provinceid) {
for (String id : cityCode) {
if (id.equals(provinceid)) {
return true;
}
}
return false;
}

/**
* 数字验证
*
*
@param str
* @return
*/
private static boolean isDigital(String str) {
return str.matches("^[0-9]*$");
}

/**
* 将身份证的每位和对应位的加权因子相乘之后,再得到和值
*
*
@param bit
* @return
*/
private static int getPowerSum(int[] bit) {

int sum = 0;

if (power.length != bit.length) {
return sum;
}

for (int i = 0; i < bit.length; i++) {
for (int j = 0; j < power.length; j++) {
if (i == j) {
sum = sum + bit[i] * power[j];
}
}
}
return sum;
}

/**
* 将和值与11取模得到余数进行校验码判断
*
*
*
@param sum17
* @return 校验位
*/
private static String getCheckCodeBySum(int sum17) {
String checkCode = null;
switch (sum17 % 11) {
case 10:
checkCode = "2";
break;
case 9:
checkCode = "3";
break;
case 8:
checkCode = "4";
break;
case 7:
checkCode = "5";
break;
case 6:
checkCode = "6";
break;
case 5:
checkCode = "7";
break;
case 4:
checkCode = "8";
break;
case 3:
checkCode = "9";
break;
case 2:
checkCode = "x";
break;
case 1:
checkCode = "0";
break;
case 0:
checkCode = "1";
break;
}
return checkCode;
}

/**
* 将字符数组转为整型数组
*
*
@param c
* @return
* @throws NumberFormatException
*/
private static int[] converCharToInt(char[] c) throws NumberFormatException {
int[] a = new int[c.length];
int k = 0;
for (char temp : c) {
a[k++] = Integer.parseInt(String.valueOf(temp));
}
return a;
}

}
CDH重启cloudera-scm-agent报错:No socket could be created — ((‘127.0.0.1’, 9001): [Errno 98] Address already in use) 解决cannot import name ‘EVENT_TYPE_OPENED’ from ‘watchdog.events’ (/root/anaconda3/lib/python3.9/site-packages/watchdog/events.py) IT 项目管理基础:衡量云的成功 90% 的亚太地区企业计划在未来 12 个月内部署人工智能:报告 适当调整工作负载以在云中取得成功 开始获得实时性能数据的优势 如何获得批准的项目的云 IT 预算 构建现代数据堆栈
  • 2023年8月
  • 2023年7月
  • 2023年6月
  • 2023年5月
  • 2023年4月
  • 2023年3月
  • 2023年1月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年7月
  • 2018年6月
  • Android (44)
  • bug清单 (70)
  • Fuchsia (15)
  • php (4)
  • python (16)
  • sklearn (1)
  • 云计算 (18)
  • 人工智能 (49)
  • chatgpt (14)
  • 提示词 (4)
  • Keras (1)
  • Tensorflow (3)
  • 深度学习 (13)
  • 大数据开发 (343)
  • CDH (4)
  • doris (15)
  • Elasticsearch (14)
  • Flink (42)
  • flume (7)
  • Hadoop (16)
  • Hbase (14)
  • Hive (29)
  • Java (46)
  • Kafka (9)
  • neo4j (5)
  • shardingsphere (6)
  • solr (4)
  • Spark (83)
  • spring (11)
  • 数据仓库 (5)
  • 数据挖掘 (6)
  • 运维 (20)
  • Docker (2)
  •