Layouts与Views

<Android诀窍> Layouts与Views

字数2466  阅读1565  评论4 

1. 统一重复组件风格

# styles.xml
<resources>

    <style name="FormRadioButton" parent="android:Widget.CompoundButton.RadioButton">
        <item name="android:minHeight">@dimen/buttonHeight</item>
        <item name="android:button">@null</item>
        <item name="android:background">@drawable/background_radio</item>
        <item name="android:gravity">center</item>
    </style>

</resources>


# activity_layout.xml
<...>

<RadioGroup
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <RadioButton
            style="@style/FormRadioButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="One"/>
        <RadioButton
            style="@style/FormRadioButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Two"/>
        <RadioButton
            style="@style/FormRadioButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Three"/>
</RadioGroup>

</...>

2. 系统界面元素触发

  • View.setSystemUiVisibility()
  • View.getSystemUiVisibility()

3. 自定义View

public class BullsEyeView extends View {
    private Paint mPaint;
    private Point mCenter;
    private float mRadius;

    /*
     * Java Constructor
     */
    public BullsEyeView(Context context) {
        this(context, null);
    }

    /*
     * XML Constructor
     */
    public BullsEyeView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /*
     * XML Constructor with Style
     */
    public BullsEyeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //在这里可以做自定义View的一些初始化配置

        //例如,创建一个可绘画的刷子
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        //设置刷子的风格
        mPaint.setStyle(Style.FILL);

        //创建绘画的中心点
        mCenter = new Point();
    }

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

        //设置大小
        int contentWidth = 200;
        int contentHeight = 200;
        width = getMeasurement(widthMeasureSpec, contentWidth);
        height = getMeasurement(heightMeasureSpec, contentHeight);

        //必须调用此方法
        setMeasuredDimension(width, height);
    }

    /*
     * 辅助设置大小的方法
     */
    private int getMeasurement(int measureSpec, int contentSize) {
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (MeasureSpec.getMode(measureSpec)) {
            case MeasureSpec.AT_MOST:
                return Math.min(specSize, contentSize);
            case MeasureSpec.UNSPECIFIED:
                return contentSize;
            case MeasureSpec.EXACTLY:
                return specSize;
            default:
                return 0;
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            //如果大小有变,要重置中心点以及半径
            mCenter.x = w / 2;
            mCenter.y = h / 2;
            mRadius = Math.min(mCenter.x, mCenter.y);
        } 
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //绘制圆形
        // 用不同颜色从最小到最大绘制
        mPaint.setColor(Color.RED);
        canvas.drawCircle(mCenter.x, mCenter.y, mRadius, mPaint);
        mPaint.setColor(Color.WHITE);
        canvas.drawCircle(mCenter.x, mCenter.y, mRadius * 0.8f, mPaint);
        mPaint.setColor(Color.BLUE);
        canvas.drawCircle(mCenter.x, mCenter.y, mRadius * 0.6f, mPaint);
        mPaint.setColor(Color.WHITE);
        canvas.drawCircle(mCenter.x, mCenter.y, mRadius * 0.4f, mPaint);
        mPaint.setColor(Color.RED);
        canvas.drawCircle(mCenter.x, mCenter.y, mRadius * 0.2f, mPaint); 
    }
}

4. View动画

  • View.animate()
  • ObjectAnimator
  • AnimatorSet

5. Layout动画

  • removeView(View)
  • addView(View, LayoutParams)

layout.xml需要添加:android:animateLayoutChanges="true"

  • Layout.setLayoutTransition(LayoutTransition)

6. 方位调整

  • 垂直:res/layout-port
  • 水平:res/layout-land

7. AdapterView空数据时的处理

  • AdapterView.setEmptyView()

8. 自定义ListView

# res/drawable/row_background_default.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:startColor="#EFEFEF"
        android:endColor="#989898"
        android:type="linear"
        android:angle="270" /> 
</shape>

# res/drawable/row_background_pressed.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:startColor="#0B8CF2"
        android:endColor="#0661E5"
        android:type="linear"
        android:angle="270" /> 
</shape>

# res/drawable/row_background.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:drawable="@drawable/row_background_pressed"/>
    <item android:drawable="@drawable/row_background_default"/>
</selector>


# res/layout/custom_row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dip"
    android:background="@drawable/row_background">
    <TextView
        android:id="@+id/line1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"    />
</LinearLayout>

9. 带字段头的ListView

# Item
public class SectionItem<T> {
    private String mTitle;
    private T[] mItems;
    public SectionItem(String title, T[] items) {
        if (title == null) title = "";
        mTitle = title;
        mItems = items;
    }
    public String getTitle() {
        return mTitle;
    }
    public T getItem(int position) {
        return mItems[position];
    }
    public int getCount() {
        //包括额外的Item以及表头
        return (mItems == null ? 1 : 1 + mItems.length);
    }

    @Override
    public boolean equals(Object object) {
        //Two sections are equal if they have the same title
        if (object != null && object instanceof SectionItem) {
            return ((SectionItem) object).getTitle().equals(mTitle);
        }
        return false;
    }
}

# Adapter
public abstract class SimpleSectionsAdapter<T> extends BaseAdapter implements AdapterView.OnItemClickListener {
    /* 常量区分表头还是表身 */
    private static final int TYPE_HEADER = 0;
    private static final int TYPE_ITEM = 1;

    private LayoutInflater mLayoutInflater;
    private int mHeaderResource;
    private int mItemResource;

    /* 数据列表 */
    private List<SectionItem<T>> mSections;

    /* 列表分组 */
    private SparseArray<SectionItem<T>> mKeyedSections;

    public SimpleSectionsAdapter(ListView parent, int headerResId, int itemResId) {
        mLayoutInflater = LayoutInflater.from(parent.getContext());
        mHeaderResource = headerResId;
        mItemResource = itemResId;

        //初始化数据
        mSections = new ArrayList<SectionItem<T>>();
        mKeyedSections = new SparseArray<SectionItem<T>>();

        //添加监听器
        parent.setOnItemClickListener(this);
    }

    /*
     * 添加新的数据
     * 或者更新
     */
    public void addSection(String title, T[] items) {
        SectionItem<T> sectionItem = new SectionItem<T>(title, items);
        //添加数据, 更新已有的数据
        int currentIndex = mSections.indexOf(sectionItem);
        if (currentIndex >= 0) {
            mSections.remove(sectionItem);
            mSections.add(currentIndex, sectionItem);
        } else {
            mSections.add(sectionItem);
        }
        //排序
        reorderSections();
        //更新试图
        notifyDataSetChanged();
    }

    /*
     * 排序
     */
    private void reorderSections() {
        mKeyedSections.clear();
        int startPosition = 0;
        for (SectionItem<T> item : mSections) {
            mKeyedSections.put(startPosition, item);
            startPosition += item.getCount();
        } 
    }

    @Override
    public int getCount() {
        int count = 0;
        for (SectionItem<T> item : mSections) {
            count += item.getCount();
        }
        return count;
    }

    @Override
    public int getViewTypeCount() {
        //两种View类型:表头,表身
        return 2; 
    }

    @Override
    public int getItemViewType(int position) {
        if (isHeaderAtPosition(position)) {
            return TYPE_HEADER;
        } else {
            return TYPE_ITEM;
        }     
    }

    @Override
    public T getItem(int position) {
        return findSectionItemAtPosition(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    /*
     * false:ListView的item不能点击
     */
    @Override
    public boolean areAllItemsEnabled() {
        return false;
    }

    /*
     * 判断哪个是ListView的表头
     */
    @Override
    public boolean isEnabled(int position) {
        return !isHeaderAtPosition(position);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        switch (getItemViewType(position)) {
            case TYPE_HEADER:
                return getHeaderView(position, convertView, parent);
            case TYPE_ITEM:
                return getItemView(position, convertView, parent);
           default:
                return convertView;
        }
    }

    private View getHeaderView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = mLayoutInflater.inflate(mHeaderResource, parent, false);
        }
        SectionItem<T> item = mKeyedSections.get(position);
        TextView textView = (TextView) convertView.findViewById(android.R.id.text1);
        textView.setText(item.getTitle());
        return convertView;
    }

    private View getItemView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = mLayoutInflater.inflate(mItemResource, parent, false);
        }
        T item = findSectionItemAtPosition(position);
        TextView textView = (TextView) convertView.findViewById(android.R.id.text1);
        textView.setText(item.toString());
        return convertView;
    }

    /** 监听点击事件 */
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        T item = findSectionItemAtPosition(position);
        if (item != null) {
            onSectionItemClick(item);
        }
    }

    /**
     * 监听某段点击事件,由用户决定
     * @item 用户点击的某个item
     */
    public abstract void onSectionItemClick(T item);

    /* 帮助映射不同的item到不同的字段内 */
    /*
     * 判断是否是表头
     */
    private boolean isHeaderAtPosition(int position) {
        for (int i=0; i < mKeyedSections.size(); i++) {
            //判断位置是否是表头
            if (position == mKeyedSections.keyAt(i)) {
                return true;
            }
        }
        return false;
    }

    /*
     * 找出全局位置的Item
     */
    private T findSectionItemAtPosition(int position) {
        int firstIndex, lastIndex;
        for (int i=0; i < mKeyedSections.size(); i++) {
            firstIndex = mKeyedSections.keyAt(i);
            lastIndex = firstIndex + mKeyedSections.valueAt(i).getCount();
            if (position >= firstIndex && position < lastIndex) {
                int sectionPosition = position - firstIndex - 1;
                return mKeyedSections.valueAt(i).getItem(sectionPosition);
            }
        }
        return null;
    }
}

10. 创建简洁易懂的自定义组件

举例TextImageButton

public class TextImageButton extends FrameLayout {
    private ImageView imageView;
    private TextView textView;

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

    public TextImageButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TextImageButton(Context context, AttributeSet attrs,
            int defaultStyle) {
        // 用默认button风格初始化layout
        // 当前主题设置了可点击属性以及背景
        super(context, attrs, android.R.attr.buttonStyle);
        //创建子类视图
        imageView = new ImageView(context, attrs, defaultStyle);
        textView = new TextView(context, attrs, defaultStyle);
        //为子类视图创建LayoutParams
        FrameLayout.LayoutParams params = new 
            FrameLayout.LayoutParams(
            LayoutParams.WRAP_CONTENT,
            LayoutParams.WRAP_CONTENT, 
            Gravity.CENTER);

        //添加子类视图
        this.addView(imageView, params);
        this.addView(textView, params);

        //如果有图片,显示图片
        if(imageView.getDrawable() != null) {
            textView.setVisibility(View.GONE);
            imageView.setVisibility(View.VISIBLE);
        } else {
            textView.setVisibility(View.VISIBLE);
            imageView.setVisibility(View.GONE);
        }
    }

    /* 属性设置 */
    public void setText(CharSequence text) {
        //切换至Text视图
        textView.setVisibility(View.VISIBLE);
        imageView.setVisibility(View.GONE);
        //设置Text值
        textView.setText(text);
    }

    public void setImageResource(int resId) {
        //切换至图片视图
        textView.setVisibility(View.GONE);
        imageView.setVisibility(View.VISIBLE);
        //设置图片资源
        imageView.setImageResource(resId);
    }

    public void setImageDrawable(Drawable drawable) {
        //切换至图片视图
        textView.setVisibility(View.GONE);
        imageView.setVisibility(View.VISIBLE);
        //设置图片Drawable
        imageView.setImageDrawable(drawable);
    }
}

11. 自定义过渡动画

  • Activity的用法

在startActivity()或者finish()后立即调用overridePendingTransition()

# startActivity()后的动画
res/anim/activity_open_enter.xml
res/anim/activity_open_exit.xml

# finish()后的动画
res/anim/activity_close_enter.xml
res/anim/activity_close_exit.xml


# res/anim/xxxx.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <rotate
        android:fromDegrees="90" android:toDegrees="0"
        android:pivotX="0%" android:pivotY="0%"
        android:fillEnabled="true"
        android:fillBefore="true" android:fillAfter="true"
        android:duration="500"  />
    <alpha
        android:fromAlpha="0.0" android:toAlpha="1.0"
        android:fillEnabled="true"
        android:fillBefore="true" android:fillAfter="true"
        android:duration="500" />
</set>

//开启新的acitvity的动画效果
Intent intent = new Intent(...);
startActivity(intent);
overridePendingTransition(R.anim.activity_open_enter, R.anim.activity_open_exit);
//关闭当前activity的动画效果
finish();
overridePendingTransition(R.anim.activity_close_enter, R.anim.activity_close_exit);

# res/values/styles.xml
<resources>
    <style name="AppTheme" parent="android:Theme.Holo.Light">
        <item name="android:windowAnimationStyle">
            @style/ActivityAnimation</item>
    </style>

    <style name="ActivityAnimation"
        parent="@android:style/Animation.Activity">
        <item name="android:activityOpenEnterAnimation">
            @anim/activity_open_enter</item>
        <item name="android:activityOpenExitAnimation">
            @anim/activity_open_exit</item>
        <item name="android:activityCloseEnterAnimation">
            @anim/activity_close_enter</item>
        <item name="android:activityCloseExitAnimation">
            @anim/activity_close_exit</item>
    </style>
</resources>
  • 支持库中的Fragments的用法:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    //必须第一个调用
    ft.setCustomAnimations(R.anim.activity_open_enter,
            R.anim.activity_open_exit,
            R.anim.activity_close_enter,
            R.anim.activity_close_exit);
    ft.replace(R.id.container_fragment, fragment);
    ft.addToBackStack(null);
ft.commit();

或者
// 覆写onCreateAnimation()方法
@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    switch (transit) {
        case FragmentTransaction.TRANSIT_FRAGMENT_FADE:
            if (enter) {
                return AnimationUtils.loadAnimation(getActivity(),
                        android.R.anim.fade_in);
            } else {
                return AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_out);
            }
        case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
            if (enter) {
                return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_close_enter);
            } else {
                return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_close_exit);
            }
        case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
        default:
            if (enter) {
                return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_open_enter);
            } else {
                return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_open_exit);
            }
    }
}

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    //设置动画效果
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
    ft.replace(R.id.container_fragment, fragment);
    ft.addToBackStack(null);
ft.commit();

12. 创建View的变化

覆写自定义View的getChildStaticTransformation()

@Override
protected boolean getChildStaticTransformation(View child, Transformation t) {
    // 清除已有的变化
    t.clear();
    if (getOrientation() == HORIZONTAL) {
        // 基于离左边缘距离来改变子类View
        float delta = 1.0f - ((float) child.getLeft() / getWidth());
        t.getMatrix().setScale(delta, delta, child.getWidth() / 2, child.getHeight() / 2);
    } else {
        //基于离上边缘距离来改变子类View
        float delta = 1.0f - ((float) child.getTop() / getHeight());
        t.getMatrix().setScale(delta, delta, child.getWidth() / 2, child.getHeight() / 2);
        //基于子类View的位置渐变
        t.setAlpha(delta);
    }
    return true;
}

13. 创建可扩展的Collection Views<待续>

<完>

阅读原文

你可能感兴趣的:(android)