آشنایی با سورس AndroidBreadCrumb
نویسنده: علی یگانه مقدم
تاریخ: ۱۳۹۴/۰۸/۰۹ ۰:۱۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
زمانی که سیستم عامل های GUI مثل ویندوز به بازار آمدند، یکی از قسمتهای گرافیکی آنها AddressBar نام داشت که مسیر حرکت آنها را در فایل سیستم نشان میداد و در سیستم عاملهای متنی CLI با دستور cd یا pwd انجام میشد. بعدها در وب هم همین حرکت با نام BreadCrumb صورت گرفت که به عنوان مثال مسیر رسیدن به صفحهی یک محصول یا یک مقاله را نشان میداد. در یک پروژهی اندرویدی نیاز بود تا یک ساختار درختی را پیاده سازی کنم، ولی در برنامههای اندروید ایجاد یک درخت، کار هوشمندانه و مطلوبی نیست و روش کار به این صورت است که یک لیست از گروههای والد را نمایش داده و با انتخاب هر آیتم لیست به آیتمهای فرزند تغییر میکند. حالا مسئله این بود که کاربر باید مسیر حرکت خودش را بشناسد. به همین علت مجبور شدم یک BreadCrumb را برای آن طراحی کنم که در زیر تصویر آن را مشاهده میکنید.
از نکات جالب توجه در مورد این ماژول میتوان گفت که قابلیت این را دارد تا تصمیمات خود را بر اساس اندازههای مختلف صفحه نمایش بگیرد. به عنوان مثال اگر آیتمهای بالا بیشتر از سه عدد باشد و در صفحه جا نشود از یک مسیر جعلی استفاده میکند و همهی آیتمها با اندیس شماره 1 تا index-3 را درون یک آیتم با عنوان (...) قرار میدهد که من به آن میگویم مسیر جعلی. به عنوان نمونه مسیر تصویر بالا در صفحه جا شده است و نیازی به این کار دیده نشده است. ولی تصویر زیر از آن جا که مسیر، طول width صفحه نمایش رد کرده است، نیاز است تا چنین کاری انجام شود. موقعیکه کاربر آیتم ... را کلیک کند، مسیر باز شده و به محل index-3 حرکت میکند. یعنی دو مرحله به عقب باز میگردد.
نگاهی به کارکرد ماژول
قبل از توضیح در مورد سورس، اجازه دهید نحوهی استفاده از آن را ببینیم.
این سورس شامل دو کلاس است که سادهترین کلاس آن AndBreadCrumbItem میباشد که مشابه کلاس ListItem در بخش وب دات نت است و دو مقدار، یکی متن و دیگری Id را میگیرد:
سورس:
public class AndBreadCrumbItem {
private int Id;
private String diplayText;
public AndBreadCrumbItem(int Id, String displayText)
{
this.Id=Id;
this.diplayText=displayText;
}
public String getDiplayText() {
return diplayText;
}
public void setDiplayText(String diplayText) {
this.diplayText = diplayText;
}
public int getId() {
return Id;
}
public void setId(int id) {
Id = id;
}
} به عنوان مثال میخواهیم یک breadcrumb را با مشخصات زیر بسازیم:
AndBreadCrumbItem itemhome=new AndBreadCrumbItem(0,"Home"); AndBreadCrumbItem itemproducts=new AndBreadCrumbItem(12,"Products"); AndBreadCrumbItem itemdigital=new AndBreadCrumbItem(15,"Digital"); AndBreadCrumbItem itemhdd=new AndBreadCrumbItem(56,"Hard Disk Drive");
AndBreadCrumb breadCrumb=new AndBreadCrumb(this);
breadCrumb.AddNewItem(itemhome);
breadCrumb.AddNewItem(itemproducts);
breadCrumb.AddNewItem(itemdigital);
breadCrumb.AddNewItem(itemhdd); breadCumb.setContext(this);
پس از افزودن آیتم ها، تنظیمات زیر را اعمال نمایید:
LinearLayout layout=(LinearLayout)getActivity().findViewById(R.id.breadcumblayout);
layout.setPadding(8, 8, 8, 8);
breadCrumb.setLayout(layout);
breadCrumb.SetTinyNextNodeImage(R.drawable.arrow);
breadCrumb.setTextSize(25);
breadCrumb.SetViewStyleId(R.drawable.list_item_style); breadCrumb.UpdatePath();
breadCrumb.setRTL(true);
در صورتیکه قصد دارید تنظیمات بیشتری چون رنگ متن، فونت متن و ... را روی هر المان اعمال کنید، از رویداد زیر استفاده کنید:
breadCrumb.setOnTextViewUpdate(new ITextViewUpdate() {
@Override
public TextView UpdateTextView(Context context, TextView tv) {
tv.setTextColor(...);
tv.setTypeface(...);
return tv;
}
}); breadCumb.setOnClickListener(new IClickListener() {
@Override
public void onClick(int position, int Id) {
//...
}
}); آخرین متد موجود که کمترین استفاده را دارد، متد SetNoResize است. در صورتیکه این متد با True مقداردهی گردد، عملیات تنظیم بر اساس صفحهی نمایش لغو میشود. این متد برای زمانی مناسب است که به عنوان مثال شما از یک HorozinalScrollView استفاده کرده باشید. در این حالت layout شما هیچ گاه به پایان نمیرسد و بهتر هست عملیات اضافه را لغو کنید.
نگاهی به سورس
کلاس زیر شامل بخشهای زیر است:
//=-=--=-=-=-=-=-=-=-=-=-=-=- Private Properties -=-=-=-=-=-=-=--=-=-=
private List<AndBreadCrumbItem> items=null;
private List<TextView> textViews;
private int tinyNextNodeImage;
private int viewStyleId;
private Context context;
private boolean RTL;
private float textSize=20;
private boolean noResize=false;
LinearLayout layout;
IClickListener clickListener;
ITextViewUpdate textViewUpdate;
LinearLayout.LayoutParams params ; //=-=---=-=-=-=-- Constructor =--=-=-=-=-=--=-=-
public AndBreadCrumb(Context context)
{
this.context=context;
params = new LinearLayout.LayoutParams
(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
}
//=-=-=--=--=-=-=-=-=-=-=-=- Public Properties --=-=-=-=-=-=--=-=-=-=-=-=-
//each category would be added to create path
public void AddNewItem(AndBreadCrumbItem item)
{
if(items==null)
items=new ArrayList<>();
items.add(item);
}
// if you want a pointer or next node between categories or textviews
public void SetTinyNextNodeImage(int resId) {this.tinyNextNodeImage=resId;}
public void SetViewStyleId(int resId) {this.viewStyleId=resId;}
public void setTextSize(float textSize) {this.textSize = textSize;}
public boolean isRTL() {
return RTL;
}
public void setRTL(boolean RTL) {
this.RTL = RTL;
}
public void setLayout(LinearLayout layout) {
this.layout = layout;
}
public void setContext(Context context) {
this.context = context;
}
public boolean isNoResize() {
return noResize;
}
public void setNoResize(boolean noResize) {
this.noResize = noResize;
} //primary method for render objects on layout
private void DrawPath() {
//stop here if essentail elements aren't present
if (items == null) return ;
if (layout == null) return;
if (items.size() == 0) return;
//we need to get size of layout,so we use the post method to run this thread when ui is ready
layout.post(new Runnable() {
@Override
public void run() {
//textviews created here one by one
int position = 0;
textViews = new ArrayList<>();
for (AndBreadCrumbItem item : items) {
TextView tv = MakeTextView(position, item.getId());
tv.setText(item.getDiplayText());
textViews.add(tv);
position++;
}
//add textviews on layout
AddTextViewsOnLayout();
//we dont manage resizing anymore
if(isNoResize()) return;
//run this code after textviews Added to get widths of them
TextView last_tv=textViews.get(textViews.size()-1);
last_tv.post(new Runnable() {
@Override
public void run() {
//define width of each textview depend on screen width
BatchSizeOperation();
}
});
}
});
} TextView tv=new TextView(this); tv.getWidth(); //return 0 layout.add(tv); tv.getWidth(); //return 0
TextView tv=new TextView(this);
tv.post(new Runnable() {
@Override
public void run() {
tv.getWidth(); //return x
}
}); private TextView MakeTextView(final int position, final int Id)
{
//settings for cumbs
TextView tv=new TextView(this.context);
tv.setEllipsize(TextUtils.TruncateAt.END);
tv.setSingleLine(true);
tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
tv.setBackgroundResource(viewStyleId);
/*call custom event - this event will be fired when user click on one of
textviews and returns position of textview and value that user sat as id
*/
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SetPosition(position);
clickListener.onClick(position, Id);
}
});
//if user wants to update each textviews
if(textViewUpdate!=null)
tv=textViewUpdate.UpdateTextView(context,tv);
if(isRTL())
tv.setRotationY(180);
return tv;
} //this method calling by everywhere to needs add textviews on the layout like master method :drawpath
private void AddTextViewsOnLayout()
{
//prepare layout
//remove everything on layout for recreate it
layout.removeAllViews();
layout.setOrientation(LinearLayout.HORIZONTAL);
layout.setVerticalGravity(Gravity.CENTER_VERTICAL);
if(isRTL())
layout.setRotationY(180);
//add textviews one by one
int position=0;
for (TextView tv:textViews)
{
layout.addView(tv,params);
//add next node image between textviews if user defined a next node image
if(tinyNextNodeImage>0)
if(position<(textViews.size()-1)) {
layout.addView(GetNodeImage(), params);
position++;
}
}
} //set textview width depend on screen width
private void BatchSizeOperation()
{
//get width of next node between cumbs
Bitmap tinyBmap = BitmapFactory.decodeResource(context.getResources(), tinyNextNodeImage);
int tinysize=tinyBmap.getWidth();
//get sum of nodes
tinysize*=(textViews.size()-1);
...
} //get width size of screen(layout is screen here)
int screenWidth=GetLayoutWidthSize();
//get sum of arrows and cumbs width
int sumtvs=tinysize;
for (TextView tv : textViews) {
int width=tv.getWidth();
sumtvs += width;
} private int GetLayoutWidthSize()
{
int width=layout.getWidth();
int padding=layout.getPaddingLeft()+layout.getPaddingRight();
width-=padding;
return width;
} private void BatchSizeOperation()
{
....
//if sum of cumbs is less than screen size the state is good so return same old textviews
if(sumtvs<screenWidth)
return ;
if(textViews.size()>3)
{
//make fake path
MakeFakePath();
//clear layout and add textviews again
AddTextViewsOnLayout();
}
//get free space without next nodes -> and spilt rest of space to textviews count to get space for each textview
int freespace =screenWidth-tinysize;
int each_width=freespace/textViews.size();
//some elements have less than each_width,so we should leave size them and calculate more space again
int view_count=0;
for (TextView tv:textViews)
{
if (tv.getWidth()<=each_width)
freespace=freespace-tv.getWidth();
else
view_count++;
}
if (view_count==0) return;
each_width=freespace/view_count;
for (TextView tv:textViews)
{
if (tv.getWidth()>each_width)
tv.setWidth(each_width);
}
} //if elements are so much(mor than 3),we make a fake path to decrease elements
private void MakeFakePath()
{
//we make 4 new elements that index 1 is fake element and has a rest of real path in its heart
//when user click on it,path would be opened
textViews=new ArrayList<>(4);
TextView[] tvs=new TextView[4];
int[] positions= {0,items.size()-3,items.size()-2,items.size()-1};
for (int i=0;i<4;i++)
{
//request for new textviews
tvs[i]=MakeTextView(positions[i],items.get(positions[i]).getId());
if(i!=1)
tvs[i].setText(items.get(positions[i]).getDiplayText());
else {
tvs[i].setText("...");
//override click event and change it to part of code to open real path by call setposition method and redraw path
tvs[i].setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = items.size() - 3;
int id = items.get(pos).getId();
SetPosition(items.size() - 3);
clickListener.onClick(pos, id);
}
});
}
textViews.add(tvs[i]);
}
}