牛骨文教育服务平台(让学习变的简单)

最近忙得好久没写博客了,今天再次回归,总结巩固自己学的东西。在android中都觉得写控件是一件比较难的事,其实并不难,这篇博客来讲讲如何自定义控件,并自定了我在项目中常用的一个控件,先看效果图:

话不多说,直接进入主题。

看看上图的圆框,我们要先画出此界面。布局如下:

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

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rel"
    android:layout_width="fill_parent"
        android:layout_height="40dp"
        android:layout_gravity="center"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"

        android:background="@drawable/global_advsearch_item_shape" >
        <TextView
            android:id="@+id/text1"
            android:layout_width="35dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_centerInParent="true"
            android:layout_marginLeft="11dp"
            android:textColor="#444444"
        />
        <View
            android:id="@+id/view1"
            android:layout_width="1dp"
            android:layout_height="15dp"
            android:layout_marginLeft="6dp"
            android:layout_centerInParent="true"
            android:layout_toRightOf="@id/text1"
            android:background="#CCCCCC" />
    <TextView
        android:id="@+id/edit1"
        android:layout_toRightOf="@id/view1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"

        android:layout_centerInParent="true"
        android:background="@null"
        android:layout_marginLeft="13dp"
        android:layout_marginRight="13dp"
        android:gravity="left|center"
        android:hint="请选择"
        android:textColor="#444444"
        />
    </RelativeLayout>

外框的背景也要用drawable下的shape来画,定义好弧度和框的颜色及粗度,如下:

<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    
    <solid android:color="#FFFFFF" />
	<stroke android:color="#F56A55"
	    android:width="1dp" />
    <corners android:radius="100dp" />

</shape>

既然是自定义的组件,那么组件的属性也要可以设置。需要定义以下的属性文件,在attrs.xml中:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="OutButton">
        <attr name="btnbackground" format="reference|color"/>
        <attr name="leftText" format="string"/>
        <attr name="leftSize" format="dimension"/>
        <attr name="leftColor" format="color"/>
        <attr name="rightColor" format="color"/>
        <attr name="rightText" format="string"/>
        <attr name="rightSize" format="dimension"/>
    </declare-styleable>

</resources>

以上的属性是随意添加的。其中leftText是控制左textView的文字,leftColor是控制文字的颜色。以此类推。format是定义属性的类型,如string是指属性要定义成字符串,dimension指的是大小,如12sp之类的。color是颜色。reference可以是引用 ,如设置图片背景的时候引用drawable下的文件。

然后就是定义我的组件,为了方便让它继承自FrameLayout,代码 如下:

public class customButton extends FrameLayout {
private TextView leftText;
    private TextView rightText;
    private RelativeLayout rel;
    private OutClickListener mlistener = null;
    private int flag;

    public customButton(final Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.outbutton, this);
        leftText = (TextView)super.findViewById(R.id.text1);
        rightText = (TextView)super.findViewById(R.id.edit1);
        TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.OutButton);
        leftText.setText(ta.getString(R.styleable.OutButton_leftText));
        rightText.setText(ta.getString(R.styleable.OutButton_rightText));
        leftText.setTextSize(ta.getDimension(R.styleable.OutButton_leftSize, 10));
        rightText.setTextSize(ta.getDimension(R.styleable.OutButton_rightSize, 10));
        //rel.setBackground(ta.getDrawable(R.styleable.OutButton_btnbackground));
        rightText.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mlistener !=null)
              mlistener.popListener();
                /*int a = mlistener.getFlag();
                Intent i = new Intent(context,CustomDialogActivity.class);
                context.ststartActivityForResult(i,1);*/
            }
        });
        ta.recycle();
    }
    public void setOutClickListener(OutClickListener listener){
        this.mlistener = listener;
    }
    public interface OutClickListener{
        void popListener();
        //int getFlag();
    }
}

其实很简单,逐一讲解。

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

指的是获取我们刚刚定义的属性文件。然后将其中定义的属性赋给控件,这样属性就与控件绑定到一起。为了使控件有点击事件,我们需要定义 一个接口OutClickListener,并在rightText的点击事件中调用接口的方法。最后通过setOnClickListener()把接口暴露给调用者,这样就可以通过回调在外层写点击事件。也保证也控件的解藕。

这样控件就定义完了,来看看如何使用。为了可以使用属性,要在布局文件的命名空间加入如下一行声明:

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

接下来就可以自定义属性了,如下:

 <com.example.linxj.customoutbutton.customButton
       android:id="@+id/btn"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_marginTop="10dp"
       lxj:leftText="时间"
       lxj:leftSize="8sp"
       lxj:rightSize ="8sp"
       lxj:rightText="请点击"></com.example.linxj.customoutbutton.customButton>

这样就可以显示了。接下来我们用这个控件来完成弹出框的功能。在一个项目中,可能会多次很到不同的弹出框,为此可以定义一个Dialog形式的Activity,并实现多个Dialog的复用。先看看代码:

public class CustomDialogActivity extends Activity {

	private List<String> dataList;
	private ListView listView;
	private Button cancelButton;
	private String[] dataSource;
	private int flag;
	private static final float RATIO = 5/10f;
	private String templeContent = "";
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		super.setContentView(R.layout.global_popwin_main);
		dataList = new ArrayList<String>();
		WindowManager.LayoutParams lp = getWindow().getAttributes();
		lp.width = LayoutParams.FILL_PARENT;
		lp.gravity = Gravity.BOTTOM;
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
		getWindow().setAttributes(lp);
		initViews();
	}
	
	private void initViews(){
		Bundle bundle = getIntent().getExtras();
		flag = bundle.getInt("flag");
		initData(flag);
		cancelButton = (Button) findViewById(R.id.cancel_but);
		listView = (ListView) findViewById(R.id.select);
		listView.setAdapter(new SessionBaseAdapter());
		listView.setOnItemClickListener(new OnItemClickListenerImpl());
		setPopWindowSize(flag);
		cancelButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Intent intent = new Intent();
				Bundle bundle = new Bundle();
				bundle.putInt("flag", flag);
				bundle.putString("content", "");
				intent.putExtras(bundle);
				setResult(RESULT_OK, intent);
				finish();
			}
		});
	}
	
	private void initData(int flag){
		dataList = new ArrayList<String>();
		switch (flag) {
			case 0:
				Bundle bundle = getIntent().getExtras();
				dataSource = bundle.getStringArray("project_year");
			case 1:
				dataSource = getResources().getStringArray(R.array.startYear);
			break;
		}
		for (int i = 0; i < dataSource.length; i++) {
			dataList.add(dataSource[i]);
		}
	}
	
	private void setPopWindowSize(int flag){
		switch (flag) {
			case 0:
			case 1:
				float screenHeight = this.getResources().getDisplayMetrics().heightPixels;
				LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) listView.getLayoutParams();
				layoutParams.height = (int) (screenHeight * RATIO);
				listView.setLayoutParams(layoutParams);
			break;
		}
	}
	
	final class ViewHolder {
		public ImageView image;
        public TextView itemName;
        public RelativeLayout layout;
    }
	
	class SessionBaseAdapter extends BaseAdapter {
		
		public SessionBaseAdapter() {}
		
		@Override
		public int getCount() {
			return dataList.size();
		}

		@Override
		public Object getItem(int postion) {
			return postion;
		}

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

		@Override
		public View getView(final int postion, View convertView, ViewGroup parent) {
			final ViewHolder holder = new ViewHolder();
			convertView = LayoutInflater.from(CustomDialogActivity.this).inflate(R.layout.global_popwin_listitem, null);
            holder.layout = (RelativeLayout) convertView.findViewById(R.id.type1_layout_1);
            holder.image = (ImageView) convertView.findViewById(R.id.icon);
            holder.itemName = (TextView) convertView.findViewById(R.id.text);
            convertView.setTag(holder);
            holder.itemName.setText(dataList.get(postion));
            holder.layout.setOnClickListener(new OnClickListener() {
				
				@Override
				public void onClick(View view) {
					holder.itemName.setTextColor(Color.parseColor("#F56A55"));
					holder.image.setVisibility(View.VISIBLE);
					templeContent = dataList.get(postion);
					startIntent();
				}
			});
			return convertView;
		}
	}
	
	private void startIntent() {

		Intent intent = new Intent();
		Bundle bundle = new Bundle();
		bundle.putInt("flag", flag);
		bundle.putString("content", templeContent);
		intent.putExtras(bundle);	
		setResult(RESULT_OK, intent);
		this.finish();
		}

	public class OnItemClickListenerImpl implements OnItemClickListener {

		@Override
		public void onItemClick(AdapterView<?> parent, View view, int postion, long id) {
			ViewHolder holder = (ViewHolder) view.getTag();
			holder.image.setVisibility(View.VISIBLE);
			holder.itemName.setTextColor(Color.parseColor("#F56A55"));
			templeContent = dataList.get(postion);
			startIntent();
		}
	}
	@Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(CustomDialogActivity.this, event)) {
        	startIntent();
            return true;
        }  
        return super.onTouchEvent(event);
    }  
  
    private boolean isOutOfBounds(Activity context, MotionEvent event) {
        final int x = (int) event.getX();
        final int y = (int) event.getY();
        final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
        final View decorView = context.getWindow().getDecorView();
        return (x < -slop) || (y < -slop)|| (x > (decorView.getWidth() + slop))|| (y > (decorView.getHeight() + slop));
    }
	
	@Override
	public void onBackPressed() {
		super.onBackPressed();
		startIntent();
	}
}

代码很长,但总结下来就是,传不同的flag进去,定义不同的Dialog样式。上面代码为了方便只举了两个flag,其实在项目中你可以定义很多分类。并定义不同样式的Dialog。

以上代码可以传不同的数组进去,它都能正常的显示出来,复用性强。接下来我们来看看MainActivity如何调用。

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        cBtn = (customButton)super.findViewById(R.id.btn);
        cBtn.setOutClickListener(new customButton.OutClickListener() {

            @Override
            public void popListener() {
                Bundle b = new Bundle();
                b.putInt("flag", 1);
                Intent i = new Intent(MainActivity.this,CustomDialogActivity.class).putExtras(b);
                startActivityForResult(i,1);

            }

        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode == RESULT_OK){
            Toast.makeText(MainActivity.this,data.getStringExtra("content"),Toast.LENGTH_SHORT).show();
        }
    }

好的,很简单,传入一个flag。而出来的年代数据是定义在布局文件string-array中的。
有的控件可能自定义进来很难,但这都是一个迭代的过程,再复杂的控件也是从简单控件入手的。之后会给出代码的下载地址。

源码下载地址